Test Report

0
0
883
945

ID Title Duration (ms)
1 array after should return an empty string when undefined. 11
2 array after should return all of the items in an array after the given index. 1
3 array after should return all of the items in an array after the specified count. 1
4 array after should return all of the items in an array after the specified count. 1
5 array arrayify should arrayify a value. 2
6 array before should return an empty string when undefined. 1
7 array before should return all of the items in an array before the given index. 1
8 array before should return all of the items in an array before the specified count. 1
9 array each should use the key and value of each property in an object inside a block. 1
10 array eachIndex should render the block using the array and each item's index. 2
11 array eachIndex should render the block when non array is passed returning empty string 1
12 array first array should return the first item in a collection. 1
13 array first array should return an array with the first two items in a collection. 0
14 array first array should return an empty string when undefined. 1
15 array first array should return the first item in an array. 0
16 array first array should return an array with the first two items in an array. 1
17 array first string should return an empty string when empty string is provided. 1
18 array first string should return the whole string when the number is big enough. 0
19 array first string should return the expected substring. 1
20 array filter should render the block if the given string is in the array. 1
21 array filter should render the inverse block if the string is not in the array: 1
22 array filter should render a block for each object that has a "first" property with the value "d". 2
23 array forEach should iterate over an array, exposing objects as context. 1
24 array forEach should expose `index` 1
25 array forEach should expose `total` 1
26 array forEach should expose `isFirst` 1
27 array forEach should expose `isLast` 1
28 array forEach should return empty block 1
29 array inArray should render the first block when a value exists in the array. 1
30 array inArray should render the inverse block when a value does not exist. 1
31 array isArray should return true if the value is an array. 2
32 array last array should return an empty string when undefined. 1
33 array last array should return the last item in an array. 1
34 array last array should return an array with the last two items in an array. 0
35 array last array should return an empty array array if non array is passed 1
36 array last string should return an empty string when empty string is provided. 1
37 array last string should return the whole string when the number is big enough. 0
38 array last string should return the expected substring. 1
39 array lengthEqual should render the first block if length is the given number 1
40 array lengthEqual should render the inverse block if length is not the given number 1
41 array map should return an empty string when undefined. 0
42 array map should map the items in the array and return new values. 1
43 array map should work with a string value: 1
44 array map should return an empty string when the array syntax is invalid: 0
45 array some should render the first block if the callback returns true 1
46 array some should render the inverse block if the array is undefined 1
47 array some should render the inverse block if falsey 1
48 array sort should return an empty string when an invalid value is passed: 0
49 array sort should sort the items in the array 1
50 array sort should return all items in an array sorted in lexicographical order. 0
51 array sort should sort the items in the array in reverse order: 1
52 array sortBy should return an empty string when undefined. 1
53 array sortBy should sort the items in an array. 0
54 array sortBy should return an empty string when the array is invalid: 1
55 array sortBy should take a compare function. 8
56 array sortBy should sort based on object key: 1
57 array withAfter should use all of the items in an array after the specified count. 0
58 array withBefore should use all of the items in an array before the specified count. 1
59 array withFirst should use the first item in an array. 1
60 array withFirst should return an empty string when no array is passed: 0
61 array withFirst should return an empty string when no array is passed: 1
62 array withFirst should use the first two items in an array. 1
63 array withLast should return an empty string when undefined. 0
64 array withLast should use the last item in an array. 1
65 array withLast should use the last two items in an array. 0
66 array withSort should return an empty string when array is undefined 1
67 array withSort should sort the array in lexicographical order 0
68 array withSort should sort the array in reverse order 0
69 array withSort should sort the array by deliveries 1
70 array withSort should sort the array by deliveries in reverse order 1
71 collection isEmpty should render the first block when an array is empty. 1
72 collection isEmpty should render the first block when the value is null. 0
73 collection isEmpty should render the second block when an array is not empty. 1
74 collection isEmpty should render the second block when an object is not empty. 1
75 collection isEmpty should render the first block when an object is empty. 0
76 collection iterate object should iterate over a plain object: 1
77 collection iterate object should expose `@key`: 1
78 collection iterate object should render the inverse block when falsey: 0
79 collection iterate array should iterate over an array: 1
80 collection iterate array should expose `@index`: 1
81 collection length should return the length of the array 0
82 collection length should return an empty string when undefined. 0
83 collection length should return empty strig when the value is null. 1
84 collection length should return the length of a string. 0
85 collection length should parse an array passed as a string 1
86 collection length should return 0 when the array is invalid: 0
87 collection length should return 0 when the value is not an array: 1
88 comparison and should render a block if both values are truthy. 1
89 comparison and should render the inverse block if both values are not truthy. 0
90 comparison gt second arg should render the first block if true. 1
91 comparison gt second arg should render the second block if equal. 0
92 comparison gt second arg should render the second block if false. 0
93 comparison gt compare hash should not render a block if the value is not equal to a given number. 1
94 comparison gt compare hash should render a block if the value is greater than a given number. 1
95 comparison gt compare hash should not render a block if the value is less than a given number. 0
96 comparison gte second argument should render the first block if true. 1
97 comparison gte second argument should render the first block if equal. 0
98 comparison gte second argument should render the second block if false. 1
99 comparison gte hash compare should render a block if the value is greater than a given number. 0
100 comparison gte hash compare should render a block if the value is equal to a given number. 1
101 comparison gte hash compare should not render a block if the value is less than a given number. 0
102 comparison has should render a block if the condition is true. 1
103 comparison has should render the inverse block if false. 1
104 comparison has should render the inverse block if value is undefined. 1
105 comparison has should render the inverse block if context is undefined. 0
106 comparison has should work with arrays 1
107 comparison has should work with two strings 1
108 comparison has should return the inverse when the second string is not found 0
109 comparison has should work with object keys 1
110 comparison eq should render a block if the value is equal to a given number. 0
111 comparison eq should render the inverse block if falsey. 0
112 comparison eq should compare first and second args 1
113 comparison ifEven should render the block if the given value is an even number 0
114 comparison ifEven should render the inverse block if the number is odd 1
115 comparison ifNth should render a custom class on even rows 2
116 comparison ifOdd should render the block if the given value is an even number 0
117 comparison ifOdd should render the inverse block if the number is odd 1
118 comparison is should render a block if the condition is true. 0
119 comparison is should use the `compare` arg on the options hash 2
120 comparison is should render the inverse if the condition is false 0
121 comparison isnt should render a block if the condition is not true. 1
122 comparison isnt should use the `compare` arg on the options hash 0
123 comparison isnt should render the inverse if the condition is false 0
124 comparison lt second arg should render the first block if true. 0
125 comparison lt second arg should render the second block if equal. 0
126 comparison lt second arg should render the second block if false. 0
127 comparison lt compare hash should render a block if the value is less than a given number. 0
128 comparison lt compare hash should not render a block if the value is greater than a given number. 1
129 comparison lte second arg should render the first block if true. 0
130 comparison lte second arg should render the first block if equal. 1
131 comparison lte second arg should render the second block if false. 0
132 comparison lte compare hash should render a block if the value is less than a given number. 0
133 comparison lte compare hash should render a block if the value is equal to a given number. 0
134 comparison lte compare hash should not render a block if the value is greater than a given number. 0
135 comparison neither should render a block if one of the values is truthy. 1
136 comparison neither should render the inverse block if neither are true. 1
137 comparison unlessEq should render a block unless the value is equal to a given number. 0
138 comparison unlessEq should render a block unless the value is equal to a given number. 1
139 comparison unlessGt should render a block unless the value is greater than a given number. 1
140 comparison unlessGt should render a block unless the value is greater than a given number. 0
141 comparison unlessLt should render a block unless the value is less than a given number. 1
142 comparison unlessLt should render a block unless the value is less than a given number. 1
143 comparison unlessGteq should render a block unless the value is greater than or equal to a given number. 0
144 comparison unlessGteq should render a block unless the value is greater than or equal to a given number. 1
145 comparison unlessGteq should not render a block unless the value is greater than or equal to a given number. 0
146 comparison unlessLteq should render a block unless the value is less than or equal to a given number. 1
147 comparison unlessLteq should render a block unless the value is less than or equal to a given number. 0
148 comparison unlessLteq should not render a block unless the value is less than or equal to a given number. 1
149 first string should extract the first char when no "n" is given. 1
150 first string should return the whole string when "n" is bigger that string size 1
151 first string should return the expected string 1
152 first string should return empty string when empty string is provided 0
153 first array should extract the first elem when no "n" is given. 1
154 first array should return the whole array when "n" is bigger that array size 1
155 first array should return the expected elems 0
156 first array should return empty string when empty array is provided 0
157 html ellipsis should return an empty string if undefined 1
158 html ellipsis should return then string truncated by a specified length. 1
159 html ellipsis should return the string if shorter than the specified length. 0
160 html sanitize should return an empty string when undefined. 0
161 html sanitize should strip html from a string. 0
162 html ul should should return an unordered list 1
163 html ol should should return an ordered list 1
164 html thumbnailImage {{{thumbnailImage context}}} should return figure with link and caption 0
165 html thumbnailImage {{{thumbnailImage context}}} should return figure with extra class "test" 1
166 html thumbnailImage {{{thumbnailImage context}}} should return figure with image that has class "test" 0
167 html thumbnailImage {{{thumbnailImage context}}} should return figure with link that has class "test" 1
168 html thumbnailImage {{{thumbnailImage context}}} should return figure without link 0
169 html thumbnailImage {{{thumbnailImage context}}} should return figure without caption 1
170 inflection inflect should return the plural or singular form of a word based on a value. 0
171 inflection inflect should return the plural or singular form of a word based on a value and include the count. 1
172 inflection ordinalize should return an ordinalized string. 1
173 last string should extract the last char when no "n" is given. 1
174 last string should return the whole string when "n" is bigger that string size 1
175 last string should return the expected string 1
176 last string should return empty string when empty string is provided 0
177 last array should extract the last elem when no "n" is given. 1
178 last array should return the whole array when "n" is bigger that array size 1
179 last array should return the expected elems 0
180 last array should return empty string when empty array is provided 1
181 markdown markdown should render markdown using the {{#markdown}} block helper 4
182 math add should return the sum of two numbers. 0
183 math average should return the average of a list of numbers: 1
184 math average should return the average of an array of numbers: 0
185 math ceil should return the value rounded up to the nearest integer. 1
186 math divide should return the division of two numbers. 0
187 math floor should return the value rounded down to the nearest integer. 1
188 math multiply should return the multiplication of two numbers. 0
189 math round should return the value rounded to the nearest integer. 1
190 math subtract should return the difference of two numbers. 0
191 math sum should return the sum of multiple numbers. 1
192 math sum should return the sum of multiple numbers. 0
193 math sum should return the total sum of array. 1
194 math sum should return the total sum of array and numbers. 0
195 misc default should use the given value: 1
196 misc default should fallback to the default value when no value exists 0
197 misc noop should be a noop 1
198 misc withHash should return an empty sting 1
199 misc withHash should not blow up when no hash is defined. 0
200 misc withHash should return the inverse hash when defined and the value is falsy. 1
201 misc withHash should return string from the newly created context 0
202 misc withHash should return string from the parent context 1
203 misc withHash should add two attributes to the new context 1
204 number phoneNumber Format a phone number. 0
205 number toFixed should return the value rounded to the nearest integer. 0
206 number toFixed should return the value rounded exactly n digits after the decimal place. 1
207 number toPrecision Returns the number in fixed-point or exponential notation rounded to n significant digits. 1
208 number toPrecision should return the value rounded exactly n digits after the decimal place. 0
209 number toExponential should return the number in fixed-point or exponential notation rounded to n significant digits. 1
210 number toExponential should return the number in fixed-point or exponential notation rounded to exactly n significant digits. 0
211 number toInt should return an integer. 1
212 number toFloat should return a floating point number. 0
213 number addCommas should add commas to a number. 0
214 number toAbbr should abbreviate the given number. 0
215 number toAbbr should abbreviate a number with to the given decimal. 0
216 number toAbbr should round up to the next increment 0
217 number toAbbr should abbreviate a number based on a number and include decimal. 1
218 number random should return a random number between two values. 1
219 object extend should extend multiple objects into one: 1
220 object extend should work as a non-helper util: 0
221 object extend should skip over sparse objects 0
222 object forIn should iterate over each property in an object: 1
223 object forIn should return the inverse block if no object is passed: 1
224 object forIn should expose private variables: 1
225 object forOwn should iterate over each property in an object: 0
226 object forOwn should return the inverse block if no object is passed: 1
227 object forOwn should only expose "own" keys: 0
228 object forOwn should expose private variables: 1
229 object toPath should return a path from provided arguments 1
230 object toPath should return a path from calculated arguments 0
231 object toPath should return a `get` compatible path 1
232 object hasOwn should return true if object has own property: 1
233 object hasOwn should return false if object does not have own property: 0
234 object isObject should return true if value is an object: 1
235 object isObject should return false if value is not an object: 0
236 object merge should deeply merge objects passed on the context: 1
237 object parseJSON should parse a JSON string: 0
238 object stringify should stringify an object: 1
239 string camelcase should return an empty string if undefined 0
240 string camelcase should return the string in camelcase 1
241 string camelcase should lowercase a single character 1
242 string capitalize should return an empty string if undefined 0
243 string capitalize should capitalize a word. 0
244 string capitalizeAll should return an empty string if undefined 1
245 string capitalizeAll should return the string with the every word capitalized. 0
246 string center should return an empty string if undefined 1
247 string center should return the string centered by using non-breaking spaces. 0
248 string chop should return an empty string if undefined 1
249 string chop should remove non-word characters from start of string 0
250 string chop should remove non-word characters from end of string 0
251 string dashcase should return an empty string if undefined 1
252 string dashcase should return the string in dashcase 1
253 string dashcase should lowercase a single character 0
254 string dotcase should return an empty string if undefined 1
255 string dotcase should return the string in dotcase 1
256 string dotcase should lowercase a single character 0
257 string hyphenate should return an empty string if undefined 1
258 string hyphenate should return the string with spaces replaced with hyphens. 0
259 string lowercase should return an empty string if undefined 0
260 string lowercase should return the string in lowercase 0
261 string pascalcase should return an empty string if undefined 1
262 string pascalcase should return the string in pascalcase 0
263 string pascalcase should uppercase a single character 1
264 string pathcase should return an empty string if undefined 0
265 string pathcase should return the string in pathcase 0
266 string pathcase should lowercase a single character 0
267 string plusify should return an empty string if undefined 0
268 string plusify should return the empty string with no change. 1
269 string plusify should return the string with no change. 0
270 string plusify should return the string with spaces replaced with pluses. 1
271 string replace should return an empty string if undefined 0
272 string replace should replace occurrences of string "A" with string "B" 0
273 string replace should return the string if `a` is undefined 0
274 string replace should replace the string with `""` if `b` is undefined 1
275 string reverse should return an empty string if undefined 0
276 string reverse should return the string in reverse. 0
277 string sentence should return an empty string if undefined 1
278 string sentence should capitalize the first word of each sentence in a string and convert the rest of the sentence to lowercase. 0
279 string snakecase should return an empty string if undefined 1
280 string snakecase should lowercase a single character 0
281 string snakecase should return the string in snakecase 1
282 string split should return an empty string if undefined 0
283 string split should split the string with the default character 1
284 string split should split the string on the given character 0
285 string titleize should return an empty string if undefined 0
286 string titleize should return unchanged input string if string has only non-word characters 1
287 string titleize should return the string in title case. 0
288 string trim should return an empty string if undefined 1
289 string trim should trim leading whitespace 0
290 string trim should trim trailing whitespace 0
291 string startsWith should return an empty string if undefined 1
292 string startsWith should render "Yes he is", from inside the block. 0
293 string startsWith should render the Inverse block. 1
294 string startsWith should render the Inverse block. 0
295 string uppercase should return an empty string if undefined 1
296 string uppercase should return the string in uppercase 0
297 string uppercase should work as a block helper 1
298 url urlResolve should take a base URL, and a href URL, and resolve them as a browser would 1
299 url urlResolve should take a base URL, and a href URL, and resolve them as a browser would 1
300 url urlResolve should take a base URL, and a href URL, and resolve them as a browser would 1
301 url encodeURI should return an encoded uri string. 0
302 url decodeURI should return an decoded uri string. 1
303 url urlParse should take a string, and return an object stringified to JSON. 0
304 url strip protocol should take an http url and return without the protocol 0
305 url strip protocol strip https protocol 0
306 url strip protocol should leave a relative url unchanged 0
307 url strip protocol should leave an absolute url unchanged 0
308 filter: should filter strings from the array: 1
309 filter: should filter objects: 0
310 filter: should filter strings: 0
311 filter: should throw an error when the callback is missing. 1
312 flatten should flatten nested arrays: 0
313 flatten should flatten deeply nested arrays: 0
314 errors should throw an error when invalid args are passed: 1
315 empty array should return an empty array when null or undefined is passed 0
316 basic sort should sort an array of primitives 0
317 arraySort should sort by a property: 1
318 arraySort should sort by a nested property: 0
319 arraySort should do nothing when the specified property is not a string: 1
320 arraySort should sort by multiple properties: 0
321 arraySort should support sorting with a list of function: 0
322 arraySort should support sorting with an array of function: 1
323 arraySort should support sorting with any combination of functions and properties: 1
324 arraySort should support reverse sorting with any combination of functions and properties: 1
325 createFrame should create a reference to _parent: 0
326 createFrame should expose a non-enumerable `extend` method: 1
327 createFrame should extend the frame object with the `extend` method: 0
328 createFrame should extend the frame object with additional objects: 0
329 createFrame should work with sparse arguments: 1
330 createFrame should add private variables when passed to `options.fn()`: 6
331 createFrame should create private variables from options hash properties 3
332 createFrame should throw an error if args are invalid: 0
333 .forIn() should loop through all properties in the object. 1
334 .forIn() should break the loop early if `false` is returned. 0
335 forOwn should expose keys and values from the given object 0
336 indexOf should get the index of the given element: 0
337 indexOf should return -1 if fromIndex is out of range: 0
338 indexOf should return -1 if the element does not exist: 0
339 indexOf should return -1 if the array is undefined 0
340 indexOf should get the index, starting from the given index: 0
341 isEven should return true if the number is odd: 0
342 isEven should work with strings: 0
343 isEven should throw an error on bad args: 0
344 isEven should throw an error on non-integer args: 0
345 isExtendable should return true when a value is an object: 0
346 isExtendable should return false when a value is not an object: 1
347 is a number 255 should be a number 0
348 is a number 5000 should be a number 0
349 is a number 0 should be a number 0
350 is a number 0.1 should be a number 1
351 is a number -0.1 should be a number 0
352 is a number -1.1 should be a number 0
353 is a number 37 should be a number 0
354 is a number 3.14 should be a number 1
355 is a number 1 should be a number 0
356 is a number 1.1 should be a number 0
357 is a number 10 should be a number 0
358 is a number 10.1 should be a number 0
359 is a number 100 should be a number 1
360 is a number -100 should be a number 0
361 is a number "0.1" should be a number 0
362 is a number "-0.1" should be a number 0
363 is a number "-1.1" should be a number 1
364 is a number "0" should be a number 0
365 is a number "012" should be a number 0
366 is a number "0xff" should be a number 0
367 is a number "1" should be a number 0
368 is a number "1.1" should be a number 0
369 is a number "10" should be a number 0
370 is a number "10.10" should be a number 0
371 is a number "100" should be a number 0
372 is a number "5e3" should be a number 0
373 is a number " 56\r\n " should be a number 1
374 is a number 0.6931471805599453 should be a number 0
375 is a number 12 should be a number 0
376 is a number 12 should be a number 0
377 is a number 1 should be a number 0
378 is a number 0 should be a number 0
379 is a number 1.5707963267948966 should be a number 0
380 is a number 0.7853981633974483 should be a number 0
381 is a number 0.4636476090008061 should be a number 0
382 is a number 1 should be a number 0
383 is a number 0.5403023058681398 should be a number 0
384 is a number 2.718281828459045 should be a number 0
385 is a number 2.718281828459045 should be a number 0
386 is a number 1 should be a number 0
387 is a number 2.302585092994046 should be a number 0
388 is a number 0.6931471805599453 should be a number 1
389 is a number 0 should be a number 0
390 is a number 0.4342944819032518 should be a number 0
391 is a number 1.4426950408889634 should be a number 0
392 is a number 2 should be a number 0
393 is a number 1 should be a number 0
394 is a number 3.141592653589793 should be a number 0
395 is a number 1 should be a number 0
396 is a number 3125 should be a number 1
397 is a number 0.5197859611550224 should be a number 0
398 is a number 1 should be a number 0
399 is a number 0.8414709848078965 should be a number 0
400 is a number 1 should be a number 0
401 is a number 0.7071067811865476 should be a number 0
402 is a number 1.4142135623730951 should be a number 0
403 is a number 1.5574077246549023 should be a number 0
404 is a number 1.7976931348623157e+308 should be a number 0
405 is a number 5e-324 should be a number 0
406 is a number "0.0" should be a number 0
407 is a number "0x0" should be a number 0
408 is a number "0e+5" should be a number 0
409 is a number "000" should be a number 0
410 is a number "0.0e-5" should be a number 0
411 is a number "0.0E5" should be a number 0
412 is a number 0 should be a number 0
413 is a number 1 should be a number 1
414 is a number 3.14 should be a number 0
415 is a number 37 should be a number 0
416 is a number 5 should be a number 0
417 is a number 0 should be a number 0
418 is a number 0 should be a number 0
419 is a number 0.6931471805599453 should be a number 0
420 is a number 1 should be a number 0
421 is a number 0 should be a number 1
422 is a number 1756258093848 should be a number 0
423 is not a number " " should not be a number 0
424 is not a number "\r\n\t" should not be a number 0
425 is not a number "" should not be a number 0
426 is not a number "" should not be a number 1
427 is not a number "3a" should not be a number 0
428 is not a number "abc" should not be a number 0
429 is not a number "false" should not be a number 0
430 is not a number "null" should not be a number 0
431 is not a number "true" should not be a number 0
432 is not a number "undefined" should not be a number 0
433 is not a number null should not be a number 0
434 is not a number null should not be a number 0
435 is not a number null should not be a number 0
436 is not a number null should not be a number 0
437 is not a number null should not be a number 0
438 is not a number null should not be a number 0
439 is not a number null should not be a number 0
440 is not a number null should not be a number 0
441 is not a number null should not be a number 1
442 is not a number {} should not be a number 0
443 is not a number [1,2,3] should not be a number 0
444 is not a number [1] should not be a number 0
445 is not a number [] should not be a number 0
446 is not a number true should not be a number 0
447 is not a number false should not be a number 0
448 is not a number null should not be a number 0
449 is not a number undefined should not be a number 1
450 is not a number null should not be a number 0
451 is not a number null should not be a number 0
452 is not a number undefined should not be a number 0
453 is not a number null should not be a number 0
454 is not a number "2025-08-27T01:28:13.849Z" should not be a number 0
455 is not a number null should not be a number 0
456 is not a number undefined should not be a number 1
457 is not a number {} should not be a number 0
458 isOdd should return true if the number is odd: 0
459 isOdd should work with strings: 1
460 isOdd should throw an error when an invalid value is passed 0
461 Same-Realm Server Tests should return `true` if the object is created by the `Object` constructor. 0
462 Same-Realm Server Tests should return `false` if the object is not created by the `Object` constructor. 1
463 kindOf null and undefined should work for undefined 0
464 kindOf null and undefined should work for null 0
465 kindOf primitives should work for booleans 0
466 kindOf primitives should work for numbers 0
467 kindOf primitives should work for strings 0
468 kindOf objects should work for arguments 0
469 kindOf objects should work for buffers 1
470 kindOf objects should work for objects 0
471 kindOf objects should work for dates 0
472 kindOf objects should work for arrays 0
473 kindOf objects should work for regular expressions 0
474 kindOf objects should work for functions 0
475 kindOf es6 features should work for Map 0
476 kindOf es6 features should work for WeakMap 1
477 kindOf es6 features should work for Set 0
478 kindOf es6 features should work for WeakSet 0
479 kindOf es6 features should work for Symbol 0
480 make iterator should return source argument if it is already a function with no context 0
481 make iterator should return a function that calls object/deepMatches if argument is an object 0
482 make iterator should return a function that calls object/deepMatches if argument is a regex 0
483 make iterator should return a function that returns the property value if argument is a string 0
484 make iterator should return a function that returns the property value if argument is a number 0
485 make iterator should return an identify function if no args 0
486 make iterator should return an identify function if first arg is `null` 1
487 make iterator should return a function that is called with the specified context 0
488 sync markdown helper should render markdown: 0
489 sync markdown helper should highlight code blocks 11
490 sync markdown helper should pass options to remarkable 24
491 sync markdown helper should pass options to highlight.js: 1
492 sync handlebars: should work as a handlebars helper: 1
493 sync handlebars: should pass hash options to remarkable: 2
494 sync handlebars: should pass hash options to highlight.js: 2
495 .mixinDeep() should deeply mix the properties of object into the first object. 0
496 .mixinDeep() should merge object properties without affecting any object 0
497 .mixinDeep() should do a deep merge 1
498 .mixinDeep() should use the last value defined 0
499 .mixinDeep() should use the last value defined on nested object 0
500 .mixinDeep() should shallow clone when an empty object is passed 0
501 .mixinDeep() should merge additional objects into the first: 0
502 .mixinDeep() should clone objects during merge 0
503 .mixinDeep() should deep clone arrays during merge 1
504 .mixinDeep() should copy source properties 0
505 .mixinDeep() should not clone arrays 0
506 .mixinDeep() should work with sparse objects: 0
507 .mixinDeep() should clone RegExps 1
508 .mixinDeep() should clone Dates 0
509 .mixinDeep() should not clone objects created with custom constructor 0
510 striptags should not modify plain text 0
511 striptags should remove simple HTML tags 1
512 striptags should leave HTML tags if specified 0
513 striptags should leave attributes when allowing HTML 0
514 striptags should leave nested HTML tags if specified 0
515 striptags should leave outer HTML tags if specified 0
516 striptags should remove DOCTYPE declaration 1
517 striptags should remove comments 0
518 striptags should strip <> within quotes 0
519 striptags should strip extra < within tags 0
520 striptags should strip tags within comments 1
521 striptags should strip comment-like tags 0
522 striptags should leave normal exclamation points alone 0
523 striptags should allow an array parameter for allowable tags 0
524 striptags should strip tags when an empty array is provided 1
525 striptags should not fail with nested quotes 0
526 all helper (with single argument) should behave like if helper 8
527 all helper should render "big" if all conditions truthy 3
528 all helper should render empty if any condition is falsy 5
529 any helper (with option hash) should return "big" with matching predicate 2
530 any helper (with option hash) should return nothing without matching predicate 4
531 any helper (with multiple arguments) should return "big" if at least one arg valid 3
532 any helper (with multiple arguments) should return "" when no arguments are valid 3
533 assignVar and getVar helpers should throw an exception if the assignVar key is not a string 1
534 assignVar and getVar helpers should throw an exception if the getVar key is not a string 1
535 assignVar and getVar helpers should assign and get variables 4
536 assignVar and getVar helpers should accept null and undefined as input before any variables are stored 1
537 assignVar and getVar helpers should return empty string if variable is not defined 0
538 assignVar and getVar helpers should return empty string if variable is not *yet* defined 1
539 assignVar and getVar helpers should return integers with correct type 1
540 assignVar and getVar helpers should accept a "SafeString" object as a valid alternative to a string (results should be a string when stored) 1
541 assignVar and getVar helpers should accept a string up to the maximum length of the buffer (Max length: 1024) 0
542 assignVar and getVar helpers should throw an error if buffer is filled (Max length: 1024) 0
543 assignVar and getVar helpers should throw an error if buffer is overflowed (Max length: 1024) 1
544 assignVar and getVar helpers should allow for the writing of up to and including maximum variables (Maximum: 50) 1
545 assignVar and getVar helpers should fail when creating more than the maximum variables (Overflow at: 51) 2
546 assignVar and getVar helpers should allow for more variable assignments when some are deleted 2
547 assignVar and getVar helpers should return undefined accessing proto/constructor 0
548 partial and block helpers should insert partial into the corresponding block 2
549 partial and block helpers should not trigger an error if partial name is empty, fallback case used instead 2
550 partial and block helpers should not trigger an error if block name is empty 1
551 partial and block helpers should successfully render template 1
552 partial and block helpers should successfully render template with context 1
553 partial and block helpers should return empty string if using a reserved object property name 2
554 cdn helper should render the css cdn url and produce resource hints when attribute resourceHint is part of the template 4
555 cdn helper should render the css cdn url and produce resource hint without as/type attribute 1
556 cdn helper should render the css cdn url 2
557 cdn helper should render normal assets cdn url 4
558 cdn helper should not use the cdn url 1
559 cdn helper should return the same value if it is a full url 3
560 cdn helper should delete any . and / from the begining of the path if this is not a full path 8
561 cdn helper should return an empty string if no path is provided 1
562 cdn helper should return a webDav asset if webdav protocol specified 2
563 cdn helper should not return a webDav asset if webdav protocol is not correct 2
564 cdn helper should return a custom CDN asset if protocol is configured 3
565 cdn helper should return a custom CDN asset when using nested helper 4
566 cdn helper should return a custom CDN asset when using nested concat helper 2
567 cdn helper should return a local CDN asset if no cdn url is configured 4
568 cdn helper should not return a custom CDN asset if protocol is not configured 1
569 cdn helper should return basic asset URL if protocol is not correct 2
570 cdn helper should avoid double slash in path 1
571 compare helper should render "big" if all compares match 5
572 compare helper should render empty for all cases 4
573 concat helper should concatanate two strings 1
574 decrementVar helper should throw an exception if the decrementVar key is not a string 0
575 decrementVar helper should correctly decrement 1
576 decrementVar helper should correctly decrement an existing variable 5
577 decrementVar helper should correctly overwrite an existing non-integer variable 0
578 decrementVar helper should correctly return data accession object proto/constructor 1
579 dynamicComponent helper should render all dynamic templates accordingly 1
580 dynamicComponent helper should return undefined when accessing proto/constructor 2
581 earlyHint should create a resource hint with only required properties 1
582 earlyHint should create a resource hint with all the properties 1
583 encodeHtmlEntities helper should return a string with HTML entities encoded 2
584 encodeHtmlEntities helper should return a string with HTML entities encoded with named references 1
585 encodeHtmlEntities helper should return a string with HTML entities encoded with decimal option 0
586 encodeHtmlEntities helper should return a string with HTML entities encoded with named references and decimal option 1
587 encodeHtmlEntities helper should return a string with HTML entities encoded with encodeEverything option 1
588 encodeHtmlEntities helper should return a string with HTML entities encoded with encodeEverything and useNamedReferences option 0
589 encodeHtmlEntities helper should return a string with HTML entities encoded with allowUnsafeSymbols option 1
590 encodeHtmlEntities helper should throw an exception if an invalid named argument is passed 1
591 encodeHtmlEntities helper should throw an exception if a non-string argument is passed 1
592 equals helper should render yes if the value is equal to 5 1
593 equals helper should render empty string 1
594 for helper should iterate once. 2
595 for helper should iterate 10 times 2
596 for helper should not iterate more than 100 times 2
597 for helper should render w/o context 2
598 for helper should convert strings to integers and iterate 10 times 1
599 for helper should not iterate if "from" is less than "to" 0
600 get helper gets a nested prop 0
601 get helper does not access prototype properties 1
602 get helper accepts SafeString paths 1
603 get helper gets context from options object if none was given 1
604 get helper renders to the empty string if no args are passed 0
605 get helper returns the object arg if it is a string, number, undefined, or null 1
606 get helper returns undefined if prop path does not exist 1
607 getContentImage helper should throw an exception if a url is passed 1
608 getContentImage helper should throw an exception if a non-string is passed 4
609 getContentImage helper should return an original image if no size is passed 1
610 getContentImage helper should return an original image if invalid sizes are passed 1
611 getContentImage helper should return a sized image 2
612 getContentImage helper should support lossy compression parameter 2
613 getContentImageSrcset helper should throw an exception if a url is passed 1
614 getContentImageSrcset helper should throw an exception if a non-string is passed 1
615 getContentImageSrcset helper should return a valid srcset 1
616 getContentImageSrcset helper should support lossy compression parameter 1
617 getFontLoaderConfig should return the expected font config 1
618 getFontsCollection should return a font link with fonts from theme settings and &display=swap when no font-display value is passed 1
619 getFontsCollection should return a font link with fonts from theme settings and &display=swap when font-display value passed is invalid 0
620 getFontsCollection should return a font link with fonts from theme settings and &display=${font-display} when valid font-display value is passed 0
621 getFontsCollection should not crash if a malformed Google font is passed when no font-display value is passed 1
622 getFontsCollection should not crash if a malformed Google font is passed when valid font-display value is passed 1
623 getFontsCollection should not crash if a malformed Google font is passed when invalid font-display value is passed 1
624 getImage helper should return a url if a url is passed 0
625 getImage helper should return empty if image is invalid 1
626 getImage helper should use the preset from _images 2
627 getImage helper should use the size from the theme_settings 0
628 getImage helper should use the default image url if image is invalid 1
629 getImage helper should use original size if not default is passed 1
630 getImage helper should default to max value (width & height) if value is not provided 1
631 getImage helper should default to size of the image dimensions if known and a larger size is requested 1
632 getImage helper should support lossy compression parameter 2
633 getImageManagerImage helper should throw an exception if a url is passed 1
634 getImageManagerImage helper should throw an exception if a non-string is passed 1
635 getImageManagerImage helper should return an original image if no size is passed 0
636 getImageManagerImage helper should return an original image if invalid sizes are passed 1
637 getImageManagerImage helper should return a sized image 2
638 getImageManagerImage helper should support lossy compression parameter 2
639 getImageManagerImageSrcset helper should throw an exception if a url is passed 0
640 getImageManagerImageSrcset helper should throw an exception if a non-string is passed 1
641 getImageManagerImageSrcset helper should return a valid srcset 1
642 getImageManagerImageSrcset helper should support lossy compression parameter 1
643 getImageSrcset helper should return a srcset if a valid image and srcset sizes are passed 2
644 getImageSrcset helper should return a srcset made of default sizes if requested 1
645 getImageSrcset helper should return a srcset made of default sizes up to the width of the image if known 1
646 getImageSrcset helper should return empty string if no parameters are passed 1
647 getImageSrcset helper should return a srcset without a descriptor if a valid image and single srcset size is passed 1
648 getImageSrcset helper should return a url if a url is passed 1
649 getImageSrcset helper should return empty if srcset array is invalid 1
650 getImageSrcset helper should return empty if image is invalid 1
651 getImageSrcset helper should use the default image url if image is invalid 1
652 getImageSrcset helper should support lossy compression parameter 2
653 getImageSrcset1x2x helper should return a srcset if a valid image and srcset sizes are passed 2
654 getImageSrcset1x2x helper should return an image url alone (1x) if the 2x size is not within constraints 1
655 getImageSrcset1x2x helper should return an image url alone (1x) if image does not have dimesions 4
656 getImageSrcset1x2x helper should throw an exception if an no size argument is passed 1
657 getImageSrcset1x2x helper should throw an exception if an invalid size argument is passed 0
658 getImageSrcset1x2x helper should support lossy compression parameter 2
659 getObject helper returns requested prop in correct object type 1
660 getObject helper does not access prototype props 1
661 getObject helper accepts SafeString paths 1
662 getShortMonth helper should render an object properly 3
663 helperMissing should return empty string if the helper is missing 0
664 if helper should have the same behavior as the original if helper 4
665 if helper should render "big" if all conditions match 5
666 if helper should render empty for all cases 4
667 if helper should ignore additional arguments and process only the first three arguments 1
668 if helper should throw an exception when non string value sent to gtnum 1
669 if helper should throw an exception when NaN value sent to gtnum 2
670 if helper should render "big" if all ifs match 4
671 if helper should render empty for all cases 8
672 if helper should work as a non-block helper when used as a subexpression 2
673 unless helper should print hello 1
674 unless helper should print empty 1
675 unless helper should work with arrays 1
676 unless helper should work as a non-block helper when used as a subexpression 1
677 incrementVar helper should throw an exception if the incrementVar key is not a string 1
678 incrementVar helper should correctly increment 1
679 incrementVar helper should correctly increment an existing variable 2
680 incrementVar helper should correctly overwrite an existing non-integer variable 1
681 incrementVar helper should correctly return data accession object proto/constructor 1
682 inject helper should inject variables 1
683 join helper should print a list of names 1
684 join helper should print a list of names and limit to 3 1
685 join helper should print a list of names and use "and" for the last name 0
686 join helper should print a list of names and limit to 3 and use "and" for the last name 1
687 json helper should render object to json format 0
688 json helper should work together with getImage 0
689 json helper should work together with concat 1
690 JSONParseSafe helper #Block: should execute the main instruction 1
691 JSONParseSafe helper #Block: should skip the main instruction if variable is non-json 1
692 JSONParseSafe helper #Block: should execute the else instruction if variable is non-json 1
693 JSONParseSafe helper Inline: Output should match the same as `main test` 0
694 JSONParseSafe helper Inline: Using JSON.stringify helper, and a empty object; parse the empty object and output it 1
695 JSONParseSafe helper Inline: Using JSON.stringify helper, and a invalid / malformed object; parse the invalid object and output it, output is a Javascript `undefined` object 1
696 JSONParseSafe helper Inline: Stringifed example - with values 0
697 JSONParseSafe helper Inline: Stringifed example - without values 1
698 JSONParseSafe helper Inline: Stringifed example - malformed input 0
699 lang helper should translate the key with attributes 0
700 lang helper should return an empty string if translator is undefined 1
701 lang helper should return an empty string if translastor returns an object 1
702 langJson helper should return translation as JSON string if translator is defined 0
703 langJson helper should return an empty object as JSON string if translator is not defined 0
704 common utils getValue should get a value from an object 0
705 common utils getValue should get nested values 1
706 common utils getValue should get nested values from arrays 0
707 common utils getValue should get nested values from objects in arrays 0
708 common utils getValue should return obj[String(path)] or undefined if path is not a string or array 0
709 common utils getValue should return the whole object if path is empty 1
710 common utils getValue should return obj if path is falsey 0
711 common utils getValue should return undefined if prop does not exist 0
712 common utils getValue should treat backslash-escaped . characters as part of a prop name 1
713 common utils getValue should not access inherited props 0
714 common utils appendLossyParam should append compression=lossy to URLs without query params 0
715 common utils appendLossyParam should append compression=lossy to URLs with existing query params 0
716 common utils appendLossyParam should not modify URL when lossy is false 0
717 common utils appendLossyParam should not modify URL when lossy is undefined 1
718 common utils appendLossyParam should not modify URL when lossy is not a boolean 0
719 resource hints addResourceHint creates resource hints when valid params are provided 0
720 resource hints addResourceHint creates resource hints when valid params are provided defaulting cors to no when no provided 1
721 resource hints addResourceHint does not add a hint when provided path is invalid 0
722 resource hints addResourceHint does create a hint when no type is provided 0
723 resource hints addResourceHint does not create a hint when provided state param is not supported 1
724 resource hints addResourceHint does not create duplicate, by src, hints, and returns the found src 0
725 resource hints addResourceHint does not create any hint when the limit of allowed hints was reached 0
726 resource hints addResourceHint should throw when invalid cors value is provided 0
727 resource hints addResourceHint should handle URLs with special chars 0
728 resource hints addResourceHint should throw when invalid URL is passed in 1
729 limit helper should limit an array properly 1
730 limit helper should limit an string properly 0
731 moment helper renders the date in the format specified 196
732 moment helper is backwards compatible with helper-date 0.2.3 null-argument behavior 3
733 moment helper permits use of moment.js functions 1
734 moment helper when amount is provided runs console.warn 1
735 money helper should correctly set money value from money site settings 1
736 money helper should read money site settings from input parameters 2
737 money helper should correctly set currency position regardless of location capitalization - [STRF-10878] 1
738 money helper should throw an exception if the price value parameter has an invalid type 0
739 money helper should throw an exception if the decimal places parameter has an invalid type 1
740 multiConcat helper should concatenate all strings by default 1
741 multiConcat helper should accept string, number, boolean, empty 2
742 nl2br helper should convert new lines to <br> tags 0
743 nonce helper should render a nonce in quotes with the correct value from request params 1
744 nonce helper should not render a nonce since request param is empty 5
745 occurrences helper should count number of occurrences of substring 2
746 occurrences helper should ignore non-strings 2
747 occurrences helper should ignore empty strings 1
748 option helper returns the nested prop of this.options 1
749 option helper does not access prototype props 0
750 option helper accepts SafeString paths 1
751 or helper should return "big" if at least one arg valid 2
752 or helper should return "" when no arguments are valid 1
753 pluck helper should get the values from all elements in collection 1
754 pluck helper should return undefined when accessing proto/constructor 0
755 pluck helper should return empty array when null is provided 1
756 pre helper should render an object properly 1
757 pre helper should scape html entities 1
758 Region Helper should return an empty container if using empty content context 0
759 Region Helper should return an empty container if no matching region on context object 1
760 Region Helper should return without region translation data attribute if no translation is provided 1
761 Region Helper should return Hello World 0
762 replace helper should replace all ocurrance of %%var%% with "day" 1
763 replace helper should handle undefined values 1
764 replace helper should replace $ 2
765 replace helper should work nicely with other helpers that use safestring 0
766 replace helper should gracefully handle not strings 1
767 resourceHints should return the expected resource links 2
768 setURLQueryParam helper should return a URL with an added parameter given correct input 1
769 setURLQueryParam helper should return a URL with an updated parameter given correct input 1
770 setURLQueryParam helper should throw an exception if a url is passed with no parameters 1
771 setURLQueryParam helper should throw an exception if a url is passed with only one parameter 1
772 setURLQueryParam helper should throw an exception if an invalid URL is passed 2
773 snippet helper should render a comment 1
774 strReplace helper should replace all by default 1
775 strReplace helper should remove characters from string 0
776 strReplace helper should replace multiple if given quantity 2
777 strReplace helper should throw an exception if the parameters have an invalid type 2
778 stripQuerystring helper strips the query string from a given url 0
779 stripQuerystring helper should work with the getImageSrcset helper as a nested expression 1
780 stripQuerystring helper should work with the getImage helper as a nested expression 1
781 stylesheet helper should render a link tag with the cdn ulr and stencil-stylesheet data tag 2
782 stylesheet helper should render both link tags correclty but produce only one resource hint 1
783 stylesheet helper should render a link tag and all extra attributes with no cdn url 0
784 stylesheet helper should render a link with empty href and no resource hint 1
785 stylesheet helper should add configId to the filename 1
786 stylesheet helper should not append configId if the file is not in assets/css/ directory 4
787 third party handlebars-helpers array helpers after helper returns all the items in an array after the index 0
788 third party handlebars-helpers array helpers first helper returns the first n items in an array 0
789 third party handlebars-helpers collection helpers length helper returns the length of the array 1
790 third party handlebars-helpers comparison helpers contains helper renders the contains block if it evaluates to true 0
791 third party handlebars-helpers comparison helpers contains helper renders the else block if it evaluates to false 1
792 third party handlebars-helpers comparison helpers contains helper renders the contains block if it evaluates to true. object as input 1
793 third party handlebars-helpers comparison helpers contains helper renders the else block if it evaluates to false. object as input 0
794 third party handlebars-helpers comparison helpers contains helper renders the else block if it evaluates to false. null / number as input 1
795 third party handlebars-helpers html helpers contains ellipsis truncates a string to the specified length and appends an ellipsis 1
796 third party handlebars-helpers html helpers contains thumbnailImage creates a <figure> with a thumbnail linked to an image 0
797 third party handlebars-helpers inflection helpers contains ordinalize returns an ordinalized number as a string 1
798 third party handlebars-helpers markdown helpers contains markdown converts a string of markdown to HTML 1
799 third party handlebars-helpers math helpers contains avg returns the average of the numbers in an array 0
800 third party handlebars-helpers object helpers contains isObject returns true if the value is an object 1
801 third party handlebars-helpers object helpers contains isObject returns false if the value is not an object 0
802 third party handlebars-helpers string helpers contains capitalize capitalizes the first word in a sentence 1
803 toLowerCase helper should convert string to lower case 1
804 toLowerCase helper should properly handle values other than strings 1
805 truncate helper should return the entire string if length is longer than the input string 1
806 truncate helper should return the first length number of characters 0
807 truncate helper should return the first argument, coerced to a string, if it is not a string 1
808 truncate helper should handle non-English strings 1
809 truncate helper should handle empty strings 1
810 truncate helper should handle null object 0
811 truncate helper should handle unicode strings 1
812 typeof inline should return string 0
813 typeof inline should return number 1
814 typeof inline should return boolean 0
815 typeof inline should return object: null 1
816 typeof inline should return object: undefined 0
817 typeof inline should return object: object 1
818 typeof inline should return object: array 0
819 typeof inline should return bigInt 1
820 typeof inline should return function 0
821 typeof inline should work with output from another function 1
822 typeof inline should not accept more than one value, throw a error 0
823 typeof inline should not work as a block helper, throw a error 1
824 helper registration loads all the helpers 1
825 switching handlebars versions defaults to v3 0
826 switching handlebars versions can load v3 0
827 switching handlebars versions can load v4 1
828 helper registration loads the helpers and registers them with handlebars 0
829 helper context puts empty site settings into the helper context when not provided 0
830 helper context puts site settings into the helper context when provided 0
831 helper context puts site settings into the helper context when provided after construction 0
832 helper context puts empty theme settings into the helper context when not provided 0
833 helper context puts theme settings into the helper context when provided 0
834 helper context puts theme settings into the helper context when provided after construction 1
835 helper context puts translator accessor into the helper context 0
836 helper context puts page content accessor into the helper context 0
837 helper context puts handlebars environment into the helper context 1
838 helper context puts a handle to global storage into the helper context 0
839 addTemplates registers preprocessed templates with handlebars 1
840 addTemplates registers raw templates with handlebars 0
841 addTemplates registers a mix of preprocessed and raw templates with handlebars 0
842 addTemplates throws FormatError if templates were precompiled with the wrong compiler version 1
843 addTemplates preProcessor throws CompileError when given bad templates 1
844 addTemplates can render using registered partials 1
845 addTemplates isTemplateLoaded tells you if a template has been loaded 0
846 addTemplates does not override templates if called twice 0
847 render can render using registered partials 0
848 render renders without a context 0
849 render sets the locale in the context if a translator is supplied 0
850 render sets the template path in the context 0
851 render applies decorators 0
852 render can use helpers 0
853 render throws TemplateNotFound if missing template 0
854 render throws RenderError if given bad template 0
855 render throws DecoratorError if decorator fails 0
856 renderString renders the given template with the given context 0
857 renderString can use helpers 1
858 renderString renders without a context 1
859 renderString throws CompileError if given a non-string template 0
860 renderString throws PrecompileError if given malformed template 0
861 errors has an accessor to get custom error classes 1
862 logging log helper uses given logger 1
863 logging log helper should not be called, when log level = error 0
864 logging log helper should not be called, when log level = warning 0
865 logging console log helper uses given logger 1
866 logging console log helper should not be called, when log level = error 1
867 logging console log helper should not be called, when log level = warning 1
868 logging should override default console.log 1
869 logging should override default console.info 0
870 logging should override default console.error 0
871 logging should override default console.warn 1
872 logging should check that property access denied message is set as info 1
873 logging should check that property access denied message is set as error 1
874 object manipulation fix shouldnt print when use renderString 1
875 object manipulation fix shouldnt print when use render 1
876 AppError is an instance of Error 0
877 AppError keeps track of the message 1
878 AppError keeps track of the extra details 0
879 AppError supplies default details hash 0
880 AppError makes the error name available 0
881 AppError does not include AppError constructor in stack trace 1
882 subclassing is an instance of Error 0
883 subclassing makes the error name available 0

Code Coverage Report

97.69%
13535
13222
313

helpers.js

100%
76
76
0
Line Lint Hits Source
1 1 const helpersList = [
2 'all',
3 'any',
4 'assignVar',
5 'block',
6 'cdn',
7 'compare',
8 'concat',
9 'contains',
10 'decrementVar',
11 'dynamicComponent',
12 'encodeHtmlEntities',
13 'for',
14 'get',
15 'getContentImage',
16 'getContentImageSrcset',
17 'getFontLoaderConfig',
18 'getFontsCollection',
19 'getImage',
20 'getImageManagerImage',
21 'getImageManagerImageSrcset',
22 'getImageSrcset',
23 'getImageSrcset1x2x',
24 'getObject',
25 'getVar',
26 'helperMissing',
27 'if',
28 'incrementVar',
29 'inject',
30 'join',
31 'jsContext',
32 'json',
33 'jsonParseSafe',
34 'lang',
35 'langJson',
36 'limit',
37 'moment',
38 'money',
39 'multiConcat',
40 'nl2br',
41 'occurrences',
42 'option',
43 'or',
44 'partial',
45 'pluck',
46 'pre',
47 'region',
48 'replace',
49 'resourceHints',
50 'setURLQueryParam',
51 'snippets',
52 'stripQuerystring',
53 'strReplace',
54 'stylesheet',
55 'thirdParty',
56 'toLowerCase',
57 'truncate',
58 'unless',
59 'earlyHint',
60 'nonce',
61 'typeof'
62 ];
63
64 1 const deprecatedHelpersList = [
65 'enumerate',
66 'equals',
67 'getShortMonth',
68 'pick'
69 ];
70
71 1 let helpers = [];
72
73 1 helpersList.forEach(helper => {
74 helpers = [...helpers, ...require(`./helpers/${helper}.js`)];
75 })
76
77 1 deprecatedHelpersList.forEach(helper => {
78 helpers = [...helpers, ...require(`./helpers/deprecated/${helper}.js`)];
79 })
80
81 // Export full list of helpers
82 1 module.exports = helpers;
83

index.js

99.58%
237
236
1
Line Lint Hits Source
1 'use strict';
2 1 const HandlebarsV3 = require('handlebars');
3 1 const HandlebarsV4 = require('@bigcommerce/handlebars-v4');
4 1 const helpers = require('./helpers');
5
6 1 const AppError = require('./lib/appError');
7 1 const { CompileError, FormatError, RenderError, DecoratorError, TemplateNotFoundError, ValidationError, PrecompileError } = require('./lib/errors');
8
9 1 const handlebarsOptions = {
10 preventIndent: true
11 };
12
13 // HandlebarsRenderer implements the interface Paper requires for its
14 // rendering needs, and does so with Handlebars.
15 class HandlebarsRenderer {
16 // Add static accessor to reference custom errors
17 static get errors() {
18 return {
19 AppError,
20 CompileError,
21 FormatError,
22 RenderError,
23 DecoratorError,
24 TemplateNotFoundError,
25 ValidationError,
26 PrecompileError,
27 };
28 }
29
30 /**
31 * Constructor
32 *
33 * @param {Object} siteSettings - Global site settings, passed to helpers
34 * @param {Object} themeSettings - Theme settings (configuration), passed to helpers
35 * @param {String} hbVersion - Which version of handlebars to use. One of ['v3', 'v4'] - defaults to 'v3'.
36 * @param {Object} logger - A console-like object to use for logging
37 * @param {String} logLevel - log level which will be overriden by renderer
38 */
39 constructor(siteSettings, themeSettings, hbVersion, logger = console, logLevel = 'info', params = {}) {
40 // Figure out which version of Handlebars to use.
41 switch(hbVersion) {
42 case 'v4':
43 62 this.handlebars = HandlebarsV4.create();
44 62 break;
45 case 'v3':
46 default:
47 615 this.handlebars = HandlebarsV3.create();
48 615 break;
49 }
50
51 677 this.logger = logger;
52 677 this._setHandlebarsLogger();
53 677 this._overrideConsoleLog();
54 677 this.setSiteSettings(siteSettings || {});
55 677 this.setThemeSettings(themeSettings || {});
56 677 this.setTranslator(null);
57 677 this.setContent({});
58 677 this.resetDecorators();
59 677 this.setLoggerLevel(logLevel);
60 677 this.setRequestParams(params);
61
62 // Build global context for helpers
63 677 this.helperContext = {
64 handlebars: this.handlebars,
65 getSiteSettings: this.getSiteSettings.bind(this),
66 getThemeSettings: this.getThemeSettings.bind(this),
67 getRequestParams: this.getRequestParams.bind(this),
68 getTranslator: this.getTranslator.bind(this),
69 getContent: this.getContent.bind(this),
70 getLogger: this.getLogger.bind(this),
71 storage: {}, // global storage used by helpers to keep state
72 resourceHints: []
73 };
74
75 // Register helpers with Handlebars
76 677 for (let i = 0; i < helpers.length; i++) {
77 113736 const spec = helpers[i];
78 113736 this.handlebars.registerHelper(spec.name, spec.factory(this.helperContext));
79 }
80
81 677 this.overrideHelpers();
82 }
83
84 getResourceHints() {
85 return this.helperContext.resourceHints;
86 }
87
88 /**
89 * Set the paper.Translator instance used to translate strings in helpers.
90 *
91 * @param {Translator} translator A paper.Translator instance used to translate strings in helpers
92 */
93 setTranslator(translator) {
94 this._translator = translator;
95 };
96
97 /**
98 * Get the paper.Translator instance used to translate strings in helpers.
99 *
100 * @return {Translator} A paper.Translator instance used to translate strings in helpers
101 */
102 getTranslator() {
103 return this._translator;
104 };
105
106 /**
107 * Set the siteSettings object containing global site settings.
108 *
109 * @param {object} settings An object containing global site settings.
110 */
111 setSiteSettings(settings) {
112 this._siteSettings = settings;
113 };
114
115 /**
116 * Get the siteSettings object containing global site settings.
117 *
118 * @return {object} settings An object containing global site settings.
119 */
120 getSiteSettings() {
121 return this._siteSettings;
122 };
123
124 /**
125 * Set the themeSettings object containing the theme configuration.
126 *
127 * @param {object} settings An object containing the theme configuration.
128 */
129 setThemeSettings(settings) {
130 this._themeSettings = settings;
131 };
132
133 /**
134 * Get the themeSettings object containing the theme configuration.
135 *
136 * @return {object} settings An object containing the theme configuration.
137 */
138 getThemeSettings() {
139 return this._themeSettings;
140 };
141
142
143 /**
144 * Set the request params object containing the request parameters.
145 *
146 * @param {object} params
147 */
148 setRequestParams(params) {
149 this._params = params;
150 }
151
152 /**
153 * Get the request params object containing the request parameters.
154 *
155 * @returns {object} params
156 */
157 getRequestParams() {
158 return this._params;
159 }
160
161 /**
162 * Reset decorator list.
163 */
164 resetDecorators() {
165 this._decorators = [];
166 };
167
168 /**
169 * Add a decorator to be applied at render time.
170 *
171 * @param {Function} decorator
172 */
173 addDecorator(decorator) {
174 this._decorators.push(decorator);
175 };
176
177 /**
178 * Setup content regions to be used by the `region` helper.
179 *
180 * @param {Object} Regions with widgets
181 */
182 setContent(regions) {
183 this._contentRegions = regions;
184 };
185
186 /**
187 * Get content regions.
188 *
189 * @param {Object} Regions with widgets
190 */
191 getContent() {
192 return this._contentRegions;
193 };
194
195 /**
196 * Get logger provided to the library
197 *
198 * @param {Object} logger
199 */
200 getLogger() {
201 return this.logger;
202 }
203
204 /**
205 * Add templates to the active set of partials. The templates can either be raw
206 * template strings, or the result coming from the preProcessor function.
207 *
208 * @param {Object} A set of templates to register with handlebars
209 */
210 addTemplates(templates) {
211 const paths = Object.keys(templates);
212
213 41 for (let i = 0; i < paths.length; i++) {
214 83 const path = paths[i];
215
216 83 if (typeof this.handlebars.partials[path] !== 'undefined') {
217 1 continue;
218 }
219
220 82 try {
221 // Check if it is a precompiled template
222 82 const template = this._tryRestoringPrecompiled(templates[path]);
223
224 // Register it with handlebars
225 81 this.handlebars.registerPartial(path, template);
226 } catch(e) {
227 1 throw new FormatError(e.message);
228 }
229 }
230 };
231
232 _tryRestoringPrecompiled(precompiled) {
233 // Let's analyze the string to make sure it at least looks
234 // something like a handlebars precompiled template. It should
235 // be a string representation of an object containing a `main`
236 // function and a `compiler` array. We do this because the next
237 // step is a potentially dangerous eval.
238 const re = /"compiler":\[.*\],"main":function/;
239 82 if (!re.test(precompiled)) {
240 // This is not a valid precompiled template, so this is
241 // a raw template that can be registered directly.
242 4 return precompiled;
243 }
244
245 // We need to take the string representation and turn it into a
246 // valid JavaScript object. eval is evil, but necessary in this case.
247 78 let template;
248 78 eval(`template = ${precompiled}`);
249
250 // Take the precompiled object and get the actual function out of it,
251 // after first testing for runtime version compatibility.
252 78 return this.handlebars.template(template);
253 }
254
255 /**
256 * Detect whether a given template has been loaded.
257 */
258 isTemplateLoaded(path) {
259 return typeof this.handlebars.partials[path] !== 'undefined';
260 }
261
262 /**
263 * Return a function that performs any preprocessing we want to do on the templates.
264 * In our case, run them through the Handlebars precompiler. This returns a string
265 * representation of an object understood by Handlebars to be a precompiled template.
266 */
267 getPreProcessor() {
268 return templates => {
269 const paths = Object.keys(templates);
270 40 const processed = {};
271
272 40 for (let i = 0; i < paths.length; i++) {
273 82 const path = paths[i];
274 82 try {
275 82 processed[path] = this.handlebars.precompile(templates[path], handlebarsOptions);
276 } catch(e) {
277 1 throw new CompileError(e.message, { path });
278 }
279 }
280 39 return processed;
281 };
282 }
283
284 /**
285 * Render a template with the given context
286 *
287 * @param {String} path The path to the template
288 * @param {Object} context The context to provide to the template
289 * @return {Promise} A promise to return the rendered template
290 * @throws [TemplateNotFoundError|RenderError|DecoratorError]
291 */
292 render(path, context) {
293 return new Promise((resolve, reject) => {
294 context = context || {};
295
296 // Add some data to the context
297 18 context.template = path;
298 18 if (this._translator) {
299 1 context.locale_name = this._translator.getLocale();
300 }
301
302 18 delete this.handlebars.compile;
303
304 // Look up the template
305 18 const template = this.handlebars.partials[path];
306 18 if (typeof template === 'undefined') {
307 1 return reject(new TemplateNotFoundError(`template not found: ${path}`));
308 }
309
310 // Render the template
311 17 let result;
312 17 try {
313 17 result = template(context);
314 } catch(e) {
315 2 return reject(new RenderError(`${e.message} : ${e.stack}`));
316 }
317
318 // Apply decorators
319 15 try {
320 15 for (let i = 0; i < this._decorators.length; i++) {
321 2 result = this._decorators[i](result);
322 }
323 } catch(e) {
324 1 return reject(new DecoratorError(e.message));
325 }
326
327 14 resolve(result);
328 });
329 };
330
331 /**
332 * Renders a string with the given context
333 *
334 * @param {String} template
335 * @param {Object} context
336 * @return {Promise}
337 * @throws [CompileError|RenderError]
338 */
339 renderString(template, context) {
340 return new Promise((resolve, reject) => {
341 let precompiled, precompiledTemplate;
342 622 context = context || {};
343
344 622 if (typeof template !== 'string') {
345 1 return reject(new CompileError('Template must be a string'));
346 }
347
348 621 try {
349 621 delete this.handlebars.compile;
350 621 precompiled = this.handlebars.precompile(template, handlebarsOptions);
351 } catch (e) {
352 6 return reject(new PrecompileError(e.message));
353 }
354
355 615 try {
356 615 eval(`precompiledTemplate = ${precompiled}`);
357 615 template = this.handlebars.template(precompiledTemplate);
358 } catch(e) {
359 return reject(new CompileError(e.message));
360 }
361
362 // Render the result
363 615 let result;
364 615 try {
365 615 result = template(context);
366 } catch(e) {
367 42 return reject(new RenderError(`${e.message} : ${e.stack}`));
368 }
369
370 573 resolve(result);
371 });
372 }
373
374 /**
375 * Internal method. Set the Handlebars logger to use the given console alternative. This is an override
376 * of https://github.com/wycats/handlebars.js/blob/148b19182d70278237a62d8293db540483a0c46c/lib/handlebars/logger.js#L22
377 */
378 _setHandlebarsLogger() {
379 // Normalize on the v4 implementation
380 this.handlebars.logger = HandlebarsV4.logger;
381
382 // Override logger.log to use the given console alternative
383 677 this.handlebars.log = this.handlebars.logger.log = (level, ...message) => {
384 level = this.handlebars.logger.lookupLevel(level);
385
386 5 if (this.handlebars.logger.lookupLevel(this.handlebars.logger.level) <= level) {
387 3 let method = this.handlebars.logger.methodMap[level];
388 3 if (typeof this.logger[method] !== 'function') {
389 1 method = 'log';
390 2 } else if (this._overrideHandlebarsAccessDeniedToPropertyMessageLevel(...message)) {
391 1 method = 'info';
392 }
393 3 this.logger[method](...message);
394 }
395 };
396 }
397
398 /**
399 * As some handlebars helpers do not use the logger, we need to override the console.log method
400 */
401 _overrideConsoleLog() {
402 this.isLoggerOverriden = false;
403 677 if (this.logger !== console) {
404 10 console.log = this.logger.info.bind(this.logger);
405 10 console.info = this.logger.info.bind(this.logger);
406 10 console.error = this.logger.error.bind(this.logger);
407 10 console.warn = this.logger.warn.bind(this.logger);
408 10 this.isLoggerOverriden = true;
409 }
410 }
411
412 _overrideHandlebarsAccessDeniedToPropertyMessageLevel(message) {
413 return this.isLoggerOverriden && message.includes('Handlebars: Access has been denied to resolve the property');
414 }
415
416 /**
417 *
418 * @param {String} level
419 */
420 setLoggerLevel(level) {
421 this.handlebars.logger.level = level;
422 }
423
424 overrideHelpers() {
425 if (this.isLoggerOverriden) {
426 10 this.handlebars.registerHelper('log', () => undefined);
427 }
428 }
429 }
430
431 1 module.exports = HandlebarsRenderer;
432

helpers/all.js

100%
27
27
0
Line Lint Hits Source
1 'use strict';
2
3 1 const utils = require('./3p/utils');
4
5 /**
6 * Yield block only if all args are valid
7 *
8 * @example
9 * {{#all items theme_settings.optionA theme_settings.optionB}} ... {{/all}}
10 */
11 1 const factory = () => {
12 return function (...args) {
13
14 // Take the last arg which is a Handlebars options object out of args array
15 const opts = args.pop();
16
17 // Check if all the args are valid / truthy
18 23 const result = args.every(function (arg) {
19 if (utils.isArray(arg)) {
20 2 return !!arg.length;
21 }
22 // If an empty object is passed, arg is false
23 35 else if (utils.isEmpty(arg) && utils.isObject(arg)) {
24 1 return false;
25 }
26 // Everything else
27 else {
28 34 return !!arg;
29 }
30 });
31
32 // If everything was valid, then "all" condition satisfied
33 23 if (result) {
34 14 return opts.fn(this);
35 } else {
36 9 return opts.inverse(this);
37 }
38 };
39 };
40
41 1 module.exports = [{
42 name: 'all',
43 factory: factory,
44 }];
45

helpers/any.js

100%
35
35
0
Line Lint Hits Source
1 'use strict';
2
3 1 const utils = require('./3p/utils');
4 1 const deepMatches = require('./3p/utils/lib/deepMatches');
5
6 /**
7 * Yield block if any object within a collection matches supplied predicate
8 *
9 * @example
10 * {{#any items selected=true}} ... {{/any}}
11 */
12 1 const factory = () => {
13 return function (...args) {
14
15 let any;
16 // Take the last arg which is a Handlebars options object out of args array
17 19 const opts = args.pop();
18 19 const predicate = opts.hash;
19
20 19 if (!utils.isEmpty(predicate)) {
21 10 if (utils.isArray(args[0])) {
22 // With options hash, we check the contents of first argument
23 6 any = args[0].some((v) => deepMatches(v, predicate));
24 }
25 } else {
26 // DEPRECATED: Moved to #or helper
27 // Without options hash, we check all the arguments
28 9 any = args.some(function (arg) {
29 if (utils.isArray(arg)) {
30 3 return !!arg.length;
31 }
32 // If an empty object is passed, arg is false
33 11 else if (utils.isEmpty(arg) && utils.isObject(arg)) {
34 1 return false;
35 }
36 // Everything else
37 else {
38 10 return !!arg;
39 }
40 });
41 }
42
43 19 if (any) {
44 7 return opts.fn(this);
45 }
46
47 12 return opts.inverse(this);
48 };
49 };
50
51 1 module.exports = [{
52 name: 'any',
53 factory: factory,
54 }];
55

helpers/assignVar.js

97.37%
38
37
1
Line Lint Hits Source
1 'use strict';
2 1 const utils = require('./3p/utils');
3 1 const max_length = 1024;
4 1 const max_keys = 50;
5 1 const { ValidationError } = require('../lib/errors');
6 1 const common = require("./lib/common");
7
8 1 const factory = globals => {
9 return function (key, value) {
10
11 // Validate that key is a string
12 if (!utils.isString(key)) {
13 1 throw new ValidationError("assignVar helper key must be a string");
14 }
15
16 // Setup storage
17 188 if (typeof globals.storage.variables === 'undefined') {
18 26 globals.storage.variables = Object.create(null);
19 }
20
21 //Check for if the assigned value is being set to null or undefined
22 188 if (!(value === null || value === undefined)) {
23
24 // Due to check for string length, we need to unwrap Handlebars.SafeString
25 179 value = common.unwrapIfSafeString(globals.handlebars, value);
26
27 // Validate that value is a string or Number (int/float)
28 179 if (!(utils.isString(value) || value === "") && !Number.isFinite(value)) {
29 throw new ValidationError("assignVar helper value must be a string or a number (integer/float)");
30 }
31
32 // Validate that string is not longer than or equal to the max length
33 179 if (utils.isString(value) && value.length >= max_length) {
34 2 throw new ValidationError(`assignVar helper value must be less than ${max_length} characters,
35 but a ${value.length} character value was set to ${key}`);
36 }
37
38 // Make sure the number of total keys is within the limit
39 177 if (Object.keys(globals.storage.variables).length >= max_keys) {
40 1 throw new ValidationError(`Unique keys in variable storage may not exceed ${max_keys} in total`);
41 }
42
43 // Store value for later use by getVar helper
44 176 globals.storage.variables[key] = value;
45 } else {
46 // Delete value from storage as it is now unset.
47 9 delete globals.storage.variables[key];
48 }
49 };
50 };
51
52 1 module.exports = [{
53 name: 'assignVar',
54 factory: factory,
55 // Expose for use in testing to prevent magic numbers that can de-sync.
56 max_length: max_length,
57 max_keys: max_keys
58 }];
59

helpers/block.js

100%
23
23
0
Line Lint Hits Source
1 'use strict';
2
3 1 const utils = require('./3p/utils');
4 1 const common = require('./lib/common.js');
5
6 1 const factory = globals => {
7 return function(name) {
8 name = common.unwrapIfSafeString(globals.handlebars, name);
9 4 if (!utils.isString(name)) {
10 1 globals.getLogger().info("Non-string passed to block helper");
11 1 return '';
12 }
13
14 3 if (Object.getOwnPropertyNames(Object.prototype).includes(name)) {
15 1 globals.getLogger().info(`Invalid name '${name}' passed to the partial helper. Returning empty string.`);
16 1 return '';
17 }
18
19 2 const options = arguments[arguments.length - 1];
20
21 /* Look for partial by name. */
22 2 const partial = globals.handlebars.partials[name] || options.fn;
23 2 return partial(this, { data: options.hash });
24 };
25 };
26
27 1 module.exports = [{
28 name: 'block',
29 factory: factory,
30 }];
31

helpers/cdn.js

83.33%
36
30
6
Line Lint Hits Source
1 'use strict';
2
3 1 const cdnify = require('./lib/cdnify');
4 1 const {addResourceHint} = require('./lib/resourceHints');
5 1 const utils = require("./3p/utils");
6
7
8 1 const factory = globals => {
9 function addHint(fullPath, options) {
10 3 return addResourceHint(
11 globals,
12 fullPath,
13 options.hash.resourceHint,
14 options.hash.as,
15 options.hash.crossorigin
16 );
17 }
18
19 677 const cdn = cdnify(globals);
20
21 677 return function (path) {
22 let fullPath = cdn(path);
23
24 50 const options = arguments[arguments.length - 1];
25
if (
options.hash
&& options.hash.resourceHint) {
26
if (
!utils.isString(fullPath)
) {
27 console.info(`Skipping resource hint creation in CDN fill path was not valid [${fullPath}]`);
28 return fullPath;
29 }
30
31 3 const hintPath = addHint(fullPath, options);
32
if (
utils.isString(hintPath)
) {
33 3 return hintPath;
34 }
35
36 console.info(`Resource hint path was invalid [${hintPath}]`);
37 }
38
39 47 return fullPath;
40 };
41 };
42
43 1 module.exports = [{
44 name: 'cdn',
45 factory: factory,
46 }];
47

helpers/compare.js

86.84%
38
33
5
Line Lint Hits Source
1 'use strict';
2 1 const { ValidationError } = require('../lib/errors');
3
4 1 const factory = () => {
5 return function(lvalue, rvalue) {
6 const options = arguments[arguments.length - 1];
7 20 var operator;
8 20 var operators;
9 20 var result;
10
11
if (
arguments.length < 3
) {
12 throw new ValidationError("Handlerbars Helper 'compare' needs 2 parameters");
13 }
14
15
operator =
options.hash.operator
||
"=="
;
16
17 20 operators = {
18 '==': function (l, r) { return l == r; },
19 '===': function (l, r) { return l === r; },
20 '!=': function (l, r) { return l != r; },
21 '!==': function (l, r) { return l !== r; },
22 '<': function (l, r) { return l < r; },
23 '>': function (l, r) { return l > r; },
24 '<=': function (l, r) { return l <= r; },
25 '>=': function (l, r) { return l >= r; },
26 'typeof': function (l, r) { return typeof l == r; }
27 };
28
29 // getOwnPropertyNames is used because checking the property
30 // directly (like operators[x]) is insecure
31 // (we could use switch instead)
32
if (
Object.getOwnPropertyNames(operators).indexOf(operator) === -1
) {
33 throw new ValidationError("Handlerbars Helper 'compare' doesn't know the operator " + operator);
34 }
35
36 20 result = operators[operator](lvalue, rvalue);
37
38 20 if (result) {
39 10 return options.fn(this);
40 } else {
41 10 return options.inverse(this);
42 }
43 };
44 };
45
46 1 module.exports = [{
47 name: 'compare',
48 factory: factory,
49 }];
50

helpers/concat.js

100%
10
10
0
Line Lint Hits Source
1 'use strict';
2
3 /**
4 * Concats two values, primarily used as a subhelper
5 * @example
6 * {{@lang (concat 'products.reviews.rating.' this) }}
7 */
8 1 const factory = globals => {
9 return function(value, otherValue) {
10 return new globals.handlebars.SafeString(value + otherValue);
11 };
12 };
13
14 1 module.exports = [{
15 name: 'concat',
16 factory: factory,
17 }];
18

helpers/contains.js

100%
18
18
0
Line Lint Hits Source
1 'use strict';
2
3
4 1 const utils = require('./3p/utils');
5
6 /**
7 * Is any value included in a collection or a string?
8 *
9 * @example
10 * {{#contains fonts "Roboto"}} ... {{/contains}}
11 * {{#contains font_path "Roboto"}} ... {{/contains}}
12 */
13 1 const factory = () => {
14 return function(container, value) {
15 const options = arguments[arguments.length - 1];
16 6 const preparedContainer = utils.isObject(container) ? Object.values(container) : container;
17 6 const contained = preparedContainer && preparedContainer.includes ? preparedContainer.includes(value) : false;
18
19 // Yield block if true
20 6 if (contained) {
21 2 return options.fn(this);
22 } else {
23 4 return options.inverse(this);
24 }
25 };
26 };
27
28 1 module.exports = [{
29 name: 'contains',
30 factory: factory,
31 }];
32

helpers/decrementVar.js

88.89%
27
24
3
Line Lint Hits Source
1 'use strict';
2 1 const utils = require('./3p/utils');
3 1 const { ValidationError } = require('../lib/errors');
4
5 1 const max_keys = 50;
6
7 1 const factory = globals => {
8 return function (key) {
9 if (!utils.isString(key)) {
10 1 throw new ValidationError("decrementVar helper key must be a string");
11 }
12
13 // Setup storage
14 9 if (typeof globals.storage.variables === 'undefined') {
15 3 globals.storage.variables = Object.create(null);
16 }
17
18 9 if (Object.hasOwnProperty.call(globals.storage.variables, key) && Number.isInteger(globals.storage.variables[key])) {
19 // Decrement value if it already exists
20 5 globals.storage.variables[key] -= 1;
21 } else {
22 // Make sure the number of total keys is within the limit
23
if (
Object.keys(globals.storage.variables).length >= max_keys
) {
24 throw new ValidationError(`Unique keys in variable storage may not exceed ${max_keys} in total`);
25 }
26 // Initialize or re-initialize value
27 4 globals.storage.variables[key] = 0;
28 }
29
30 // Return current value
31
return
Object.hasOwnProperty.call(globals.storage.variables, key)
? globals.storage.variables[key] :
undefined
;
32 };
33 };
34
35 1 module.exports = [{
36 name: 'decrementVar',
37 factory: factory,
38 }];
39

helpers/dynamicComponent.js

80.95%
21
17
4
Line Lint Hits Source
1 'use strict';
2
3 1 const Path = require('path');
4
5 1 const factory = globals => {
6 return function(path) {
7
if (
!this['partial']
) {
8 return;
9 }
10
11 // prevent access to __proto__
12 // or any hidden object properties
13 9 path = path.replace('__', '');
14
15 // We don't want a slash as a prefix
16
if (
path[0] === '/'
) {
17 path = path.substr(1);
18 }
19
20 9 path = Path.join(path, this['partial']);
21
22 9 if (globals.handlebars.partials[path] && Object.hasOwnProperty.call(globals.handlebars.partials, path)) {
23 3 return globals.handlebars.partials[path](this);
24 }
25 };
26 };
27
28 1 module.exports = [{
29 name: 'dynamicComponent',
30 factory: factory,
31 }];
32

helpers/earlyHint.js

89.29%
28
25
3
Line Lint Hits Source
1 'use strict';
2
3 1 const {addResourceHint} = require('./lib/resourceHints');
4
5 1 const factory = globals => {
6 return function (href, rel) {
7 const options = arguments[arguments.length - 1];
8
9 2 let cors, as = undefined;
10
11
if (
options
&& options.hash) {
12 2 cors = options.hash.crossorigin;
13 2 as = options.hash.as;
14 }
15
16 2 try {
17 2 return addResourceHint(
18 globals,
19 href,
20 rel,
21 as,
22 cors
23 );
24 } catch (e) {
25 console.error(`Early hint generation failed for path [${href}]`, e);
26 throw e;
27 }
28 }
29 };
30
31 1 module.exports = [{
32 name: 'earlyHint',
33 factory
34 }];
35

helpers/encodeHtmlEntities.js

96.97%
33
32
1
Line Lint Hits Source
1 'use strict';
2 1 const he = require('he');
3 1 const utils = require('./3p/utils');
4 1 const common = require('./lib/common.js');
5 1 const { ValidationError } = require('../lib/errors');
6
7 1 const factory = globals => {
8 return function (string) {
9 string = common.unwrapIfSafeString(globals.handlebars, string);
10 15 if (!utils.isString(string)) {
11 1 throw new ValidationError("Non-string passed to encodeHtmlEntities");
12 }
13
14 14 const options = arguments[arguments.length - 1];
15
16 14 let args = {};
17
18
if (
utils.isOptions(options)
) {
19 14 args = options.hash;
20
21 // Whitelist of allowed named arguments into `he` function
22 14 const allowedArguments = [
23 'useNamedReferences',
24 'decimal',
25 'encodeEverything',
26 'allowUnsafeSymbols'
27 ];
28
29 // Make sure all named arguments from options hash are in the whitelist and have boolean (string) values
30 14 if (Object.keys(args).some(key => !allowedArguments.includes(key))
31 || !Object.keys(args).map(key => args[key]).every(val => ['true', 'false'].includes(val))) {
32 2 throw new ValidationError("Invalid named argument passed to encodeHtmlEntities");
33 }
34 }
35
36 12 return new globals.handlebars.SafeString(he.encode(string, args));
37 };
38 };
39
40 1 module.exports = [{
41 name: 'encodeHtmlEntities',
42 factory: factory,
43 }];
44

helpers/for.js

100%
37
37
0
Line Lint Hits Source
1 'use strict';
2
3 1 const utils = require('./3p/utils');
4
5 1 const factory = () => {
6 return function (from, to, context) {
7 const options = arguments[arguments.length - 1];
8 23 const maxIterations = 100;
9 23 var output = '';
10
11 23 if (utils.isOptions(to)) {
12 1 context = {};
13 1 to = from;
14 1 from = 1;
15
16 22 } else if (utils.isOptions(context)) {
17 15 if (utils.isObject(to)) {
18 2 context = to;
19 2 to = from;
20 2 from = 1;
21 }
22 }
23
24 23 if (to < from) {
25 1 return;
26 }
27
28 22 from = parseInt(from, 10);
29 22 to = parseInt(to, 10);
30
31 22 if ((to - from) >= maxIterations) {
32 2 to = from + maxIterations - 1;
33 }
34
35 22 for (var i = from; i <= to; i++) {
36 759 context.$index = i;
37 759 output += options.fn(context);
38 }
39
40 21 return output;
41 };
42 };
43
44 1 module.exports = [{
45 name: 'for',
46 factory: factory,
47 }];
48

helpers/get.js

80%
25
20
5
Line Lint Hits Source
1 'use strict';
2
3 1 const { getValue } = require('./lib/common');
4
5 /**
6 * Based on https://github.com/helpers/handlebars-helpers/blob/0.9.0/lib/object.js#L128-L134
7 *
8 * Get a value from the given context object. Property paths (`a.b.c`) may be used
9 * to get nested properties.
10 */
11 1 const factory = (globals) => {
12 return function (path, context) {
13 let options = arguments[arguments.length - 1];
14
15 // for backwards compatibility
16
if (context === null || (typeof context !== 'object' &&
typeof context !== 'function'
)) {
17 5 return context;
18 }
19
20 // use an empty context if none was given
21
if (
!context
) {
22 context = {};
23 }
24
25 // for backwards compatibility: safely use options hash as context if no context was passed
26 11 if (arguments.length < 3) {
27 1 context = { hash: options.hash };
28 }
29
30 11 let value = getValue(context, path, globals);
31
if (
options
&& options.fn) {
32
return
value
? options.fn(value) :
options.inverse(context)
;
33 }
34 10 return value;
35 };
36 };
37
38 1 module.exports = [{
39 name: 'get',
40 factory: factory,
41 }];

helpers/getContentImage.js

100%
14
14
0
Line Lint Hits Source
1 'use strict';
2 1 const { getObjectStorageImage } = require('./lib/getObjectStorageImage')
3
4 1 const factory = globals => {
5 return function(path) {
6 const siteSettings = globals.getSiteSettings();
7
8 17 const cdnUrl = siteSettings.cdn_url || '';
9
10 17 const options = arguments[arguments.length - 1];
11
12 17 return getObjectStorageImage(globals.handlebars, cdnUrl, 'content', path, options);
13 };
14 };
15
16 1 module.exports = [{
17 name: 'getContentImage',
18 factory: factory,
19 }];
20

helpers/getContentImageSrcset.js

100%
14
14
0
Line Lint Hits Source
1 'use strict';
2 1 const { getObjectStorageImageSrcset } = require('./lib/getObjectStorageImage')
3
4 1 const factory = globals => {
5 return function(path) {
6 const siteSettings = globals.getSiteSettings();
7
8 7 const cdnUrl = siteSettings.cdn_url || '';
9 7 const options = arguments[arguments.length - 1];
10
11 7 return getObjectStorageImageSrcset(globals.handlebars, cdnUrl, 'content', path, options);
12 };
13 };
14
15 1 module.exports = [{
16 name: 'getContentImageSrcset',
17 factory: factory,
18 }];
19

helpers/getFontLoaderConfig.js

100%
12
12
0
Line Lint Hits Source
1 'use strict';
2
3 1 const getFonts = require('./lib/fonts');
4
5 1 const factory = globals => {
6 return function() {
7 const fontConfig = getFonts('webFontLoaderConfig', globals.getThemeSettings(), globals.handlebars);
8 1 return new globals.handlebars.SafeString(JSON.stringify(fontConfig));
9 };
10 };
11
12 1 module.exports = [{
13 name: 'getFontLoaderConfig',
14 factory: factory,
15 }];
16

helpers/getFontsCollection.js

100%
14
14
0
Line Lint Hits Source
1 'use strict';
2
3 1 const getFonts = require('./lib/fonts');
4
5 1 const factory = globals => {
6 return function () {
7 const options = arguments[arguments.length - 1];
8 6 const fontDisplay = options.hash['font-display'];
9 6 const getFontsOptions = {globals, fontDisplay};
10 6 return getFonts('linkElements', globals.getThemeSettings(), globals.handlebars, getFontsOptions);
11 };
12 };
13
14 1 module.exports = [{
15 name: 'getFontsCollection',
16 factory: factory,
17 }];
18

helpers/getImage.js

85.71%
42
36
6
Line Lint Hits Source
1 'use strict';
2
3 1 const utils = require('./3p/utils');
4 1 const common = require('./lib/common.js');
5
6 1 const factory = globals => {
7 return function (image, presetName, defaultImageUrl) {
8 var sizeRegex = /^(\d+?)x(\d+?)$/g;
9
var settings =
globals.getThemeSettings()
||
{}
;
10 21 var presets = settings._images;
11 21 var size;
12 21 var width;
13 21 var height;
14
15 // Get options object to access hash parameters
16 21 const options = arguments[arguments.length - 1];
17
const lossy =
options
&&
options.hash
&& options.hash.lossy;
18
19
if (!utils.isObject(image) ||
!utils.isString(image.data)
20
||
!common.isValidURL(image.data)
|| image.data.indexOf('{:size}') === -1) {
21 // return empty string if not a valid image object
22 5 defaultImageUrl = defaultImageUrl ? defaultImageUrl : '';
23 5 return utils.isString(image) ? image : defaultImageUrl;
24 }
25
26 16 if (utils.isObject(presets) && utils.isObject(presets[presetName])) {
27 // If preset is one of the given presets in _images
28 11 width = parseInt(presets[presetName].width, 10) || common.maximumPixelSize;
29 11 height = parseInt(presets[presetName].height, 10) || common.maximumPixelSize;
30 11 size = `${width}x${height}`;
31 5 } else if (sizeRegex.test(settings[presetName])) {
32 // If preset name is a setting and match the NNNxNNN format
33 3 size = settings[presetName];
34 3 width = parseInt(size.split('x')[0], 10);
35 3 height = parseInt(size.split('x')[1], 10);
36 } else {
37 // Use the original image size
38 2 size = 'original';
39 }
40
41
if (Number.isInteger(image.width) &&
Number.isInteger(image.height)
42
&&
Number.isInteger(width)
&& Number.isInteger(height)) {
43 2 size = `${Math.min(image.width, width)}x${Math.min(image.height, height)}`
44 }
45
46 16 const processedUrl = image.data.replace('{:size}', size);
47 16 const finalUrl = common.appendLossyParam(processedUrl, lossy);
48
49 16 return new globals.handlebars.SafeString(finalUrl);
50 };
51 };
52
53 1 module.exports = [{
54 name: 'getImage',
55 factory: factory,
56 }];
57

helpers/getImageManagerImage.js

100%
14
14
0
Line Lint Hits Source
1 'use strict';
2 1 const { getObjectStorageImage } = require('./lib/getObjectStorageImage')
3
4 1 const factory = globals => {
5 return function(path) {
6 const siteSettings = globals.getSiteSettings();
7
8 17 const cdnUrl = siteSettings.cdn_url || '';
9
10 17 const options = arguments[arguments.length - 1];
11
12 17 return getObjectStorageImage(globals.handlebars, cdnUrl, 'image-manager', path, options);
13 };
14 };
15
16 1 module.exports = [{
17 name: 'getImageManagerImage',
18 factory: factory,
19 }];
20

helpers/getImageManagerImageSrcset.js

100%
14
14
0
Line Lint Hits Source
1 'use strict';
2 1 const { getObjectStorageImageSrcset } = require('./lib/getObjectStorageImage')
3
4 1 const factory = globals => {
5 return function(path) {
6 const siteSettings = globals.getSiteSettings();
7
8 7 const cdnUrl = siteSettings.cdn_url || '';
9 7 const options = arguments[arguments.length - 1];
10
11 7 return getObjectStorageImageSrcset(globals.handlebars, cdnUrl, 'image-manager', path, options);
12 };
13 };
14
15 1 module.exports = [{
16 name: 'getImageManagerImageSrcset',
17 factory: factory,
18 }];
19

helpers/getImageSrcset.js

93.94%
66
62
4
Line Lint Hits Source
1 'use strict';
2 1 const utils = require('./3p/utils');
3 1 const common = require('./lib/common');
4
5 1 const factory = globals => {
6 return function (...args) {
7 args.pop();
8 22 let [image, defaultImageUrl] = args;
9 // Regex to test size string is of the form 123x123 or 100w
10 22 const sizeRegex = /(^\d+w$)|(^(\d+?)x(\d+?)$)/;
11 // Regex to test to that srcset descriptor is of the form 1x 1.5x 2x OR 123w
12 22 const descriptorRegex = /(^\d+w$)|(^([0-9](\.[0-9]+)?)x)$/;
13
14 22 const options = arguments[arguments.length - 1];
15
const lossy =
options
&&
options.hash
&& options.hash.lossy;
16
17 22 if (utils.isUndefined(defaultImageUrl)) {
18 20 defaultImageUrl = '';
19 }
20
21
if (!utils.isObject(image) ||
!utils.isString(image.data)
22
||
!common.isValidURL(image.data)
|| image.data.indexOf('{:size}') === -1) {
23 // return empty string if not a valid image object
24 6 defaultImageUrl = defaultImageUrl ? defaultImageUrl : '';
25 6 return utils.isString(image) ? image : defaultImageUrl;
26 }
27
28 16 let srcsets = {};
29
30 16 if (options.hash['use_default_sizes']) {
31 2 if (Number.isInteger(image.width) && Number.isInteger(image.height)) {
32 /* If we know the image dimensions, don't generate srcset sizes larger than the image */
33 1 srcsets[`${image.width}w`] = `${image.width}w`;
34 1 const defaultSrcsetSizes = [2560, 1920, 1280, 960, 640, 320, 160, 80];
35 1 defaultSrcsetSizes.forEach(width => {
36 if (width < image.width) {
37 6 srcsets[`${width}w`] = `${width}w`;
38 }
39 });
40 } else {
41 /* If we DON'T know the image dimensions, generate a default set of srcsets
42 * This will upsize images */
43 1 srcsets = {
44 '2560w': '2560w',
45 '1920w': '1920w',
46 '1280w': '1280w',
47 '960w': '960w',
48 '640w': '640w',
49 '320w': '320w',
50 '160w': '160w',
51 '80w': '80w',
52 };
53 }
54 } else {
55 14 srcsets = Object.assign({}, options.hash);
56 // Remove non-srcset parameters like 'lossy'
57 14 delete srcsets.lossy;
58
59
if (
!utils.isObject(srcsets)
|| Object.keys(srcsets).some(descriptor => {
60 return !(descriptorRegex.test(descriptor) && sizeRegex.test(srcsets[descriptor]));
61 })) {
62 // return empty string if not valid srcset object
63 2 return ''
64 }
65 }
66
67 // If there's only one argument, return a `src` only (also works for `srcset`)
68 14 if (Object.keys(srcsets).length === 1) {
69 6 const processedUrl = image.data.replace('{:size}', srcsets[Object.keys(srcsets)[0]]);
70 6 const finalUrl = common.appendLossyParam(processedUrl, lossy);
71 6 return new globals.handlebars.SafeString(finalUrl);
72 }
73
74 8 return new globals.handlebars.SafeString(Object.keys(srcsets).reverse().map(descriptor => {
75 const processedUrl = image.data.replace('{:size}', srcsets[descriptor]);
76 29 const finalUrl = common.appendLossyParam(processedUrl, lossy);
77 29 return ([finalUrl, descriptor].join(' '));
78 }).join(', '));
79 };
80 };
81
82 1 module.exports = [{
83 name: 'getImageSrcset',
84 factory: factory,
85 }];
86

helpers/getImageSrcset1x2x.js

85.11%
47
40
7
Line Lint Hits Source
1 'use strict';
2 1 const utils = require('./3p/utils');
3 1 const common = require('./lib/common');
4 1 const { ValidationError } = require('../lib/errors');
5
6 1 const factory = globals => {
7 return function (image, size1x) {
8 // Regex to test size string is of the form 123x123
9 const pixelDimensionsRegex = /(^\d+w$)|(^(\d+?)x(\d+?)$)/;
10 13 const options = arguments[arguments.length - 1];
11
const lossy =
options
&&
options.hash
&& options.hash.lossy;
12
13 13 const return1x = (image, size1x) => {
14 const processedUrl = image.data.replace('{:size}', size1x);
15 5 const finalUrl = common.appendLossyParam(processedUrl, lossy);
16 5 return new globals.handlebars.SafeString(finalUrl);
17 };
18
19
if (!utils.isObject(image) ||
!utils.isString(image.data)
20
||
!common.isValidURL(image.data)
|| image.data.indexOf('{:size}') === -1) {
21 2 throw new ValidationError("Invalid StencilImage passed to getImageSrcset1x2x")
22 }
23
24
if (
!pixelDimensionsRegex.test(size1x)
) {
25 throw new ValidationError("Invalid size argument passed to getImageSrcset1x2x, must be a set of pixel dimensions of the format '123x123'")
26 }
27
28
if (!image.width ||
!image.height
||
!Number.isInteger(image.width)
|| !Number.isInteger(image.height)) {
29 2 return return1x(image, size1x);
30 }
31
32 9 const [width1x, height1x] = size1x.split('x').map(i => parseInt(i));
33
34
if (
width1x > image.width
35 || width1x > image.height) {
36 // Either the image is too small to make a srcset with a 2x size,
37 // or those sizes would be larger than the resizer supports
38 2 return return1x(image, size1x);
39 } else {
40 7 const smallestFactor = Math.min((image.width / width1x), (image.height / height1x));
41 7 const factor = smallestFactor < 2 ? smallestFactor : 2;
42 7 const roundedFactor = +(factor).toFixed(4) //cast to Number for clean rounding
43
44 7 const [widthXx, heightXx] = [width1x, height1x].map(i => Math.round(i * factor));
45
46 7 if (widthXx > common.maximumPixelSize || heightXx > common.maximumPixelSize) {
47 1 return return1x(image, size1x);
48 }
49
50 6 const sizeXx = `${widthXx}x${heightXx}`;
51
52 6 const url1x = common.appendLossyParam(image.data.replace('{:size}', size1x), lossy);
53 6 const urlXx = common.appendLossyParam(image.data.replace('{:size}', sizeXx), lossy);
54
55 6 return new globals.handlebars.SafeString(`${url1x} 1x, ${urlXx} ${roundedFactor}x`);
56 }
57 };
58 };
59
60 1 module.exports = [{
61 name: 'getImageSrcset1x2x',
62 factory: factory,
63 }];
64

helpers/getObject.js

87.10%
31
27
4
Line Lint Hits Source
1 'use strict';
2
3 1 const { getValue } = require('./lib/common');
4
5 /*
6 * Based on https://github.com/helpers/handlebars-helpers/blob/0.9.0/lib/object.js#L149-L151
7 * and https://github.com/jonschlinkert/get-object/blob/0.2.0/index.js#L12-L24
8 *
9 * Get an object or array containing a value from the given context object.
10 * Property paths (`a.b.c`) may be used to get nested properties.
11 */
12 1 const factory = (globals) => {
13 return function (...args) {
14 args.pop();
15 5 let [path, context] = args;
16 // use an empty context if none was given
17 // (expect 3 args: `path`, `context`, and the `options` object
18 // Handlebars always passes as the last argument to a helper)
19
if (
arguments.length < 2
) {
20 context = {};
21 }
22
23
if (
!path
) {
24 // return entire context object if no property/path specified
25 return context;
26 }
27
28 5 path = String(path);
29
30 5 const parts = path.split(/[[.\]]/).filter(Boolean);
31
32 5 let value = getValue(context, parts, globals);
33
34 // for backwards compatibility: `get-object` returns on empty object instead of
35 // getting props with 'false' values (not just undefined)
36 5 if (!value) {
37 1 return {};
38 }
39
40 // return an array if the final path part is numeric to mimic behavior of `get-object`
41 4 const last = parts[parts.length - 1];
42 4 if (Number.isFinite(Number(last))) {
43 1 return [ value ];
44 }
45 3 let result = {};
46 3 result[last] = value
47 3 return result;
48 };
49 };
50
51 1 module.exports = [{
52 name: 'getObject',
53 factory: factory,
54 }];

helpers/getVar.js

100%
15
15
0
Line Lint Hits Source
1 'use strict';
2
3 1 const utils = require('./3p/utils');
4 1 const { ValidationError } = require('../lib/errors');
5
6 1 const factory = globals => {
7 return function (key) {
8 if (!utils.isString(key)) {
9 1 throw new ValidationError("getVar helper key must be a string");
10 }
11
12 194 return globals.storage.variables && Object.hasOwnProperty.call(globals.storage.variables, key) ? globals.storage.variables[key] : undefined
13 };
14 };
15
16 1 module.exports = [{
17 name: 'getVar',
18 factory: factory,
19 }];
20

helpers/helperMissing.js

100%
10
10
0
Line Lint Hits Source
1 'use strict';
2
3 1 const factory = () => {
4 return function() {
5 return undefined;
6 };
7 };
8
9 1 module.exports = [{
10 name: 'helperMissing',
11 factory: factory,
12 }];
13

helpers/if.js

97.37%
76
74
2
Line Lint Hits Source
1 'use strict';
2
3 1 const utils = require('./3p/utils');
4 1 const { ValidationError } = require('../lib/errors');
5
6 1 const factory = globals => {
7 return function (lvalue, operator, rvalue) {
8 const options = arguments[arguments.length - 1];
9 79 let result;
10
11 // Only parameter
12 79 if (utils.isOptions(operator)) {
13 // If an array is passed as the only parameter
14 21 if (utils.isArray(lvalue)) {
15 3 result = !!lvalue.length;
16 }
17 // If an empty object is passed, treat as false
18 18 else if (utils.isEmpty(lvalue) && utils.isObject(lvalue)) {
19 1 result = false;
20 }
21 // Everything else
22 else {
23 17 result = !!lvalue;
24 }
25 } else {
26
27 58 if (utils.isOptions(rvalue)) {
28 // @TODO: this block is for backwards compatibility with 'compare' helper
29 // Remove after operator='==' is removed from stencil theme
30 20 rvalue = operator;
31
operator =
options.hash.operator
||
"=="
;
32 }
33
34 58 switch (operator) {
35 case '==':
36 4 result = (lvalue == rvalue);
37 4 break;
38
39 case '===':
40 8 result = (lvalue === rvalue);
41 8 break;
42
43 case '!=':
44 4 result = (lvalue != rvalue);
45 4 break;
46
47 case '!==':
48 6 result = (lvalue !== rvalue);
49 6 break;
50
51 case '<':
52 7 result = (lvalue < rvalue);
53 7 break;
54
55 case '>':
56 4 result = (lvalue > rvalue);
57 4 break;
58
59 case '<=':
60 4 result = (lvalue <= rvalue);
61 4 break;
62
63 case '>=':
64 4 result = (lvalue >= rvalue);
65 4 break;
66
67 case 'gtnum':
68 9 if (typeof lvalue === 'string' && typeof (rvalue) === 'string' && !isNaN(lvalue) && !isNaN(rvalue)) {
69 3 result = parseInt(lvalue) > parseInt(rvalue);
70 } else {
71 6 throw new ValidationError("if gtnum only accepts numbers (as strings)");
72 }
73 3 break;
74
75 case 'typeof':
76 8 result = (typeof lvalue === rvalue);
77 8 break;
78
79 default:
80 throw new ValidationError("Handlerbars Helper 'if' doesn't know the operator " + operator);
81 }
82 }
83
84 73 if (!options.fn || !options.inverse) {
85 2 options.fn = () => true;
86 2 options.inverse = () => false;
87 }
88
89 73 if (result) {
90 36 return options.fn(this);
91 } else {
92 37 return options.inverse(this);
93 }
94 };
95 };
96
97 1 module.exports = [{
98 name: 'if',
99 factory: factory,
100 }];
101

helpers/incrementVar.js

88.89%
27
24
3
Line Lint Hits Source
1 'use strict';
2 1 const utils = require('./3p/utils');
3 1 const { ValidationError } = require('../lib/errors');
4
5 1 const max_keys = 50;
6
7 1 const factory = globals => {
8 return function (key) {
9 if (!utils.isString(key)) {
10 1 throw new ValidationError("incrementVar helper key must be a string");
11 }
12
13 // Setup storage
14 9 if (typeof globals.storage.variables === 'undefined') {
15 3 globals.storage.variables = Object.create(null);
16 }
17
18 9 if (Object.hasOwnProperty.call(globals.storage.variables, key) && Number.isInteger(globals.storage.variables[key])) {
19 // Increment value if it already exists
20 5 globals.storage.variables[key] += 1;
21 } else {
22 // Make sure the number of total keys is within the limit
23
if (
Object.keys(globals.storage.variables).length >= max_keys
) {
24 throw new ValidationError(`Unique keys in variable storage may not exceed ${max_keys} in total`);
25 }
26 // Initialize or re-initialize value
27 4 globals.storage.variables[key] = 0;
28 }
29
30 // Return current value
31
return
Object.hasOwnProperty.call(globals.storage.variables, key)
? globals.storage.variables[key] :
undefined
;
32 };
33 };
34
35 1 module.exports = [{
36 name: 'incrementVar',
37 factory: factory,
38 }];
39

helpers/inject.js

87.50%
16
14
2
Line Lint Hits Source
1 'use strict';
2
3 1 const factory = globals => {
4 return function(key, value) {
5
if (
typeof value === 'function'
) {
6 return;
7 }
8
9 // Setup storage
10 2 if (typeof globals.storage.inject === 'undefined') {
11 1 globals.storage.inject = Object.create(null);
12 }
13
14 // Store value for later use by jsContext
15 2 globals.storage.inject[key] = value;
16 };
17 };
18
19 1 module.exports = [{
20 name: 'inject',
21 factory: factory,
22 }];
23

helpers/join.js

87.50%
24
21
3
Line Lint Hits Source
1 'use strict';
2
3 1 const factory = () => {
4 return function(array, separator) {
5 const options = arguments[arguments.length - 1];
6
var config =
options.hash
||
{}
;
7
8
if (
!Array.isArray(array)
) {
9 throw new TypeError("Non-array passed to join helper");
10 }
11 5 array = array.slice();
12
13 // Truncate array
14 5 if (config.limit && array.length > config.limit) {
15 2 array = array.slice(0, config.limit);
16 }
17
18 // Use lastSeparator between last and second last item, if provided
19 5 if (config.lastSeparator) {
20 2 var truncatedArray = array.slice(0, -1),
21 lastItem = array.slice(-1);
22
23 2 return truncatedArray.join(separator) + config.lastSeparator + lastItem;
24 }
25
26 3 return array.join(separator);
27 };
28 };
29
30 1 module.exports = [{
31 name: 'join',
32 factory: factory,
33 }];
34

helpers/jsContext.js

90.91%
11
10
1
Line Lint Hits Source
1 'use strict';
2
3 // Used in conjunction with 'inject'
4 1 const factory = globals => {
5 return function() {
6
const jsContext = JSON.stringify(JSON.stringify(
globals.storage.inject
||
{}
)); // TODO: why double stringify??
7 1 return new globals.handlebars.SafeString(jsContext);
8 };
9 };
10
11 1 module.exports = [{
12 name: 'jsContext',
13 factory: factory,
14 }];
15

helpers/json.js

100%
12
12
0
Line Lint Hits Source
1 'use strict';
2 1 const common = require('./lib/common.js');
3
4 1 const factory = globals => {
5 return function(data) {
6 data = common.unwrapIfSafeString(globals.handlebars, data);
7 7 return JSON.stringify(data);
8 };
9 };
10
11 1 module.exports = [{
12 name: 'json',
13 factory: factory,
14 }];
15

helpers/jsonParseSafe.js

100%
21
21
0
Line Lint Hits Source
1 'use strict';
2
3 function factory(globals) {
4 677 return function (value) {
5 const options = arguments[arguments.length - 1];
6
7 9 try {
8 9 if (options.fn) {
9 3 return options.fn(JSON.parse(value));
10 }
11 6 return JSON.parse(value);
12 } catch (err) {
13 4 if (options.fn) { // If function block {{#JSONparseSafe "{}"}} escape here on excaption.
14 2 return options.inverse(this);
15 }
16 2 return undefined; // Escape hatch to avoid crash due to inline malformed JSON input.
17 }
18 };
19 }
20
21 1 module.exports = [{
22 name: 'JSONparseSafe',
23 factory: factory,
24 }];
25

helpers/lang.js

100%
18
18
0
Line Lint Hits Source
1 'use strict';
2
3 1 const factory = globals => {
4 return function(translationKey) {
5 const options = arguments[arguments.length - 1];
6 3 const translator = globals.getTranslator();
7 3 if (translator) {
8 2 const result = translator.translate(translationKey, options.hash);
9 2 if (typeof result === 'string') {
10 1 return result;
11 }
12 }
13 2 return ''
14 };
15 };
16
17 1 module.exports = [{
18 name: 'lang',
19 factory: factory,
20 }];
21

helpers/langJson.js

100%
12
12
0
Line Lint Hits Source
1 'use strict';
2
3 1 const factory = globals => {
4 return function(keyFilter) {
5 const translator = globals.getTranslator();
6 2 const langJson = translator ? translator.getLanguage(keyFilter) : {};
7 2 return JSON.stringify(langJson);
8 };
9 };
10
11 1 module.exports = [{
12 name: 'langJson',
13 factory: factory,
14 }];
15

helpers/limit.js

88.24%
17
15
2
Line Lint Hits Source
1 'use strict';
2
3 1 const utils = require('./3p/utils');
4
5 /**
6 * Limit an array to the second argument
7 *
8 * @example
9 * {{limit array 4}}
10 */
11 1 const factory = () => {
12 return function (data, limit) {
13 if (utils.isString(data)) {
14 1 return data.substring(0, limit);
15 }
16
if (
!utils.isArray(data)
) {
17 return [];
18 }
19
20 1 return data.slice(0, limit);
21 };
22 };
23
24 1 module.exports = [{
25 name: 'limit',
26 factory: factory,
27 }];
28

helpers/moment.js

76.60%
47
36
11
Line Lint Hits Source
1 'use strict';
2
3 1 const utils = require('./3p/utils');
4 1 const chrono = require('chrono-node');
5
6 // suppress error messages that are not actionable
7 1 const moment = require('moment');
8 1 moment.suppressDeprecationWarnings = true;
9
10 /*
11 * Modified from https://github.com/helpers/helper-date/blob/1.0.1/index.js
12 */
13 1 const factory = () => {
14 return function (str, pattern) {
15 // always use the Handlebars-generated options object
16 let options = arguments[arguments.length - 1];
17
18 // Ideally we'd check how many args were passed and set `str` and `pattern` to
19 // null if they weren't explicitly provided. However, doing so could break
20 // backwards compatibility since we previously depended on helper-date@0.2.3.
21
22 // if no args are passed, return a formatted date
23 /* eslint no-eq-null: ["off"] */
24 12 if (str == null && pattern == null) {
25 2 moment.locale('en');
26 2 return moment().format('MMMM DD, YYYY');
27 }
28
29 10 var defaults = { lang: 'en', date: new Date(str) };
30 10 var opts = utils.context(this, defaults, options);
31
32 // set the language to use
33
moment.locale(
opts.lang
||
opts.language
);
34
35
if (
opts.datejs === false
) {
36 return moment(new Date(str)).format(pattern);
37 }
38
39 // if both args are strings, this could apply to either lib.
40 // so instead of doing magic we'll just ask the user to tell
41 // us if the args should be passed to date.js or moment.
42 10 if (typeof str === 'string' && typeof pattern === 'string') {
43 6 return moment(chrono.parseDate(str)).format(pattern);
44 }
45
46
47 // If handlebars, expose moment methods as hash properties
48
if (
options
&& options.hash) {
49
if (
options.context
) {
50 options.hash = Object.assign({}, options.hash, options.context);
51 }
52
53 4 var res = moment(str);
54 4 for (var key in options.hash) {
55 // prevent access to prototype methods
56 5 if (Object.keys(moment.prototype).indexOf(key) !== -1 && typeof res[key] === 'function') {
57 4 return res[key](options.hash[key]);
58 } else {
59 1 console.warn('moment.js does not support "' + key + '"');
60 }
61 }
62 }
63
64 if (utils.isObject(str)) {
65 return moment(str).format(pattern);
66 }
67
68 // if only a string is passed, assume it's a date pattern ('YYYY')
69 if (typeof str === 'string' && !pattern) {
70 return moment().format(str);
71 }
72
73 return moment(str).format(pattern);
74 };
75 };
76
77 1 module.exports = [{
78 name: "moment",
79 factory: factory,
80 }]

helpers/money.js

89.19%
37
33
4
Line Lint Hits Source
1 'use strict';
2 1 const { ValidationError } = require('../lib/errors');
3
4 /**
5 * Format numbers
6 *
7 * @param integer n: length of decimal
8 * @param mixed s: thousands delimiter
9 * @param mixed c: decimal delimiter
10 */
11 function numberFormat(value, n, s, c) {
12
var re = '\\d(?=(\\d{3})+' + (
n > 0
? '\\D' :
'$'
) + ')',
13 num = value.toFixed(Math.max(0, ~~n));
14
15
return (
c
? num.replace('.', c) :
num
).replace(new RegExp(re, 'g'), '$&' + (
s
||
','
));
16 }
17
18 1 const factory = globals => {
19 return function(...args) {
20 const siteSettings = globals.getSiteSettings();
21 9 const money = siteSettings.money;
22
23 // remove options hash object
24 9 args.pop();
25
26 9 let value = args[0];
27
28 9 if (isNaN(value)) {
29 1 throw new ValidationError("money helper accepts only Number's as first parameter");
30 }
31
32 8 const decimalPlaces = args[1] || money.decimal_places;
33
34 8 if (isNaN(decimalPlaces)) {
35 1 throw new ValidationError("money helper accepts only Number's for decimal places");
36 }
37 7 const thousandsToken = args[2] || money.thousands_token;
38 7 const decimalToken = args[3] || money.decimal_token;
39
40 7 value = numberFormat(
41 value,
42 decimalPlaces,
43 thousandsToken,
44 decimalToken
45 );
46
47
return
money.currency_location.toLowerCase() === 'left'
48 ? money.currency_token + ' ' + value
49
:
value + ' ' + money.currency_token
;
50 };
51 };
52
53 1 module.exports = [{
54 name: 'money',
55 factory: factory,
56 }];
57

helpers/multiConcat.js

100%
11
11
0
Line Lint Hits Source
1 'use strict';
2
3 /**
4 * Concats multi values, primarily used as a subhelper
5 * @example
6 * {{multiConcat "string1" "string2" "string3"}}
7 */
8
9
10 1 const factory = globals => {
11 return function(...args) {
12 // Take the last arg which is a Handlebars options object out of args array
13 args.pop();
14
15 313 return args.join('');
16 };
17
18 };
19
20
21 1 module.exports = [{
22 name: 'multiConcat',
23 factory: factory,
24 }];
25

helpers/nl2br.js

100%
11
11
0
Line Lint Hits Source
1 'use strict';
2
3 // https://github.com/danharper/Handlebars-Helpers/blob/master/src/helpers.js#L89
4 1 const factory = globals => {
5 return function(text) {
6 var nl2br = (globals.handlebars.escapeExpression(text) + '').replace(/([^>\r\n]?)(\r\n|\n\r|\r|\n)/g, '$1' + '<br>' + '$2');
7 1 return new globals.handlebars.SafeString(nl2br);
8 };
9 };
10
11 1 module.exports = [{
12 name: 'nl2br',
13 factory: factory,
14 }];
15

helpers/nonce.js

92.86%
14
13
1
Line Lint Hits Source
1 'use strict';
2
3 1 const factory = globals => {
4 return function() {
5 const params = globals.getRequestParams();
6
if (
params
&&
params.security
&& params.security.nonce) {
7 1 return new globals.handlebars.SafeString(params.security.nonce);
8 }
9 1 return ''
10 };
11 };
12
13 1 module.exports = [{
14 name: 'nonce',
15 factory: factory,
16 }];
17

helpers/occurrences.js

100%
23
23
0
Line Lint Hits Source
1 'use strict';
2
3 /**
4 * Based on https://github.com/helpers/handlebars-helpers/blob/0.8.4/lib/string.js#L221-L233
5 *
6 * Return the number of occurrences of `substring` within the
7 * given `string`.
8 *
9 * ```handlebars
10 * {{occurrences "foo bar foo bar baz" "foo"}}
11 * //=> 2
12 * ```
13 * @param {String} `str`
14 * @param {String} `substring`
15 * @return {Number} Number of occurrences
16 */
17
18 1 const factory = () => {
19 return function(str, substring) {
20 if (typeof str !== 'string' || str.length === 0) {
21 4 return 0;
22 }
23
24 8 if (typeof substring !== 'string' || substring.length === 0) {
25 4 return 0;
26 }
27
28 4 const len = substring.length;
29 4 let pos = 0;
30 4 let numOccurrences = 0;
31
32 4 while ((pos = str.indexOf(substring, pos)) > -1) {
33 6 pos += len;
34 6 numOccurrences++;
35 }
36
37 4 return numOccurrences;
38 };
39 };
40
41 1 module.exports = [{
42 name: 'occurrences',
43 factory: factory,
44 }];
45

helpers/option.js

85%
20
17
3
Line Lint Hits Source
1 'use strict';
2
3 1 const utils = require('./3p/utils');
4 1 const { getValue } = require('./lib/common');
5
6 /**
7 * Based on https://github.com/helpers/handlebars-helpers/blob/0.9.0/lib/misc.js#L26-L28
8 *
9 * Get a value from the options object. Property paths (`a.b.c`) may be used
10 * to get nested properties.
11 */
12 1 const factory = (globals) => {
13 return function (path, locals) {
14 // preserve `option` behavior with missing args while ensuring the correct
15 // options object is used
16 let options = arguments[arguments.length - 1];
17
if (
arguments.length < 3
) {
18 3 locals = null;
19 }
20
if (
arguments.length < 2
) {
21 path = '';
22 }
23
24 3 let opts = utils.options(this, locals, options);
25
26 3 return getValue(opts, path, globals);
27 };
28 };
29
30 1 module.exports = [{
31 name: "option",
32 factory: factory,
33 }]

helpers/or.js

100%
26
26
0
Line Lint Hits Source
1 'use strict';
2
3 1 const utils = require('./3p/utils');
4
5 /**
6 * Yield block if any object within a collection matches supplied predicate
7 *
8 * @example
9 * {{#or 1 0 0 0 0 0}} ... {{/or}}
10 */
11 1 const factory = () => {
12 return function (...args) {
13
14 // Take the last arg which is a Handlebars options object out of args array
15 const opts = args.pop();
16
17 // Evaluate all args in args array to see if any are truthy
18 6 const any = args.some(function (arg) {
19 if (utils.isArray(arg)) {
20 3 return !!arg.length;
21 }
22 // If an empty object is passed, arg is false
23 8 else if (utils.isEmpty(arg) && utils.isObject(arg)) {
24 1 return false;
25 }
26 // Everything else
27 else {
28 7 return !!arg;
29 }
30 });
31
32 6 if (any) {
33 4 return opts.fn(this);
34 }
35
36 2 return opts.inverse(this);
37 };
38 };
39
40 1 module.exports = [{
41 name: 'or',
42 factory: factory,
43 }];
44

helpers/partial.js

100%
22
22
0
Line Lint Hits Source
1 'use strict';
2
3 1 const utils = require('./3p/utils');
4 1 const common = require('./lib/common.js');
5
6 1 const factory = globals => {
7 return function(name) {
8 name = common.unwrapIfSafeString(globals.handlebars, name);
9 7 if (!utils.isString(name)) {
10 3 globals.getLogger().info("Non-string passed to partial helper");
11 3 return '';
12 }
13
14 4 if (Object.getOwnPropertyNames(Object.prototype).includes(name)) {
15 1 globals.getLogger().info(`Invalid name '${name}' passed to the partial helper. Returning empty string.`);
16 1 return '';
17 }
18
19 3 const options = arguments[arguments.length - 1];
20 3 globals.handlebars.registerPartial(name, options.fn);
21 };
22 };
23
24 1 module.exports = [{
25 name: 'partial',
26 factory: factory,
27 }];
28

helpers/pluck.js

100%
13
13
0
Line Lint Hits Source
1 'use strict';
2
3 1 const factory = () => {
4 return function(collection, path) {
5 if (collection && Array.isArray(collection)) {
6 5 return collection.map(item => Object.hasOwnProperty.call(item, path) ? item[path] : undefined);
7 }
8 2 return [];
9 };
10 };
11
12 1 module.exports = [{
13 name: 'pluck',
14 factory: factory,
15 }];
16

helpers/pre.js

100%
12
12
0
Line Lint Hits Source
1 1 'use restrict';
2
3 1 const factory = globals => {
4 return function(value) {
5 var string = JSON.stringify(value, null, 2);
6
7 2 string = globals.handlebars.Utils.escapeExpression(string);
8
9 2 return '<pre>' + string + '</pre>';
10 };
11 };
12
13 1 module.exports = [{
14 name: 'pre',
15 factory: factory,
16 }];
17

helpers/region.js

88.89%
18
16
2
Line Lint Hits Source
1 'use strict';
2
3 1 const factory = globals => {
4 return function(params) {
5 let regionId = params.hash.name;
6 4 let regionTranslation = params.hash.translation;
7 4 let contentRegions = globals.getContent();
8
9
if (
!contentRegions
) {
10 return '';
11 }
12 4 const translationDataAttribute = regionTranslation ? ` data-content-region-translation="${regionTranslation}"` : '';
13
14 4 const content = `<div data-content-region="${regionId}"${translationDataAttribute}>${contentRegions[regionId] || ''}</div>`;
15
16 4 return new globals.handlebars.SafeString(content);
17 };
18 };
19
20 1 module.exports = [{
21 name: 'region',
22 factory: factory,
23 }];
24

helpers/replace.js

100%
25
25
0
Line Lint Hits Source
1 'use strict';
2 1 const common = require('./lib/common.js');
3
4 1 const factory = globals => {
5 return function(needle, haystack) {
6 needle = common.unwrapIfSafeString(globals.handlebars, needle);
7 9 haystack = common.unwrapIfSafeString(globals.handlebars, haystack);
8 9 const options = arguments[arguments.length - 1];
9
10 9 if (typeof needle !== 'string') {
11 3 return options.inverse(this);
12 }
13
14 6 const regex = new RegExp(escapeRegex(needle), 'g');
15
16 // Yield block if true
17 6 if (typeof haystack === 'string' && regex.test(haystack)) {
18 5 return haystack.replace(regex, options.fn(this));
19 } else {
20 1 return options.inverse(this);
21 }
22 };
23 };
24
25 function escapeRegex(string) {
26 6 return string.replace(/[-\\^$*+?.()|[\]{}]/g, "\\$&");
27 }
28
29 1 module.exports = [{
30 name: 'replace',
31 factory: factory,
32 }];
33

helpers/resourceHints.js

88.68%
53
47
6
Line Lint Hits Source
1 'use strict';
2
3 1 const utils = require('./3p/utils');
4 1 const getFonts = require('./lib/fonts');
5 1 const {
6 addResourceHint,
7 resourceHintAllowedStates,
8 resourceHintAllowedTypes,
9 resourceHintAllowedCors
10 } = require('./lib/resourceHints');
11
12 1 const fontResources = {
13 'Google': [
14 'https://fonts.googleapis.com',
15 'https://fonts.gstatic.com',
16 ],
17 };
18
19 function format(host) {
20 2 return `<link rel="dns-prefetch preconnect" href="${host}" crossorigin>`;
21 }
22
23 1 const factory = globals => {
24 return function () {
25
26 let hosts = [];
27
28 // Add cdn
29 1 const siteSettings = globals.getSiteSettings();
30
const cdnUrl =
siteSettings['cdn_url']
|| '';
31
if (
utils.isString(cdnUrl)
) {
32 hosts.push(cdnUrl);
33 }
34
35 // Add font providers
36 1 const fonts = getFonts('providerLists', globals.getThemeSettings(), globals.handlebars);
37 1 for (const provider in fonts) {
38
if (
typeof fontResources[provider] !== 'undefined'
) {
39 1 hosts = hosts.concat(fontResources[provider]);
40 }
41 }
42
43 1 hosts = hosts.map(host => {
44 try {
45 2 return addResourceHint(
46 globals,
47 host,
48 resourceHintAllowedStates.preconnectResourceHintState,
49 resourceHintAllowedTypes.resourceHintFontType,
50 resourceHintAllowedCors.anonymousCors
51 );
52 } catch (e) {
53 console.info(`EarlyHint generation failed in resourceHints helper with host [${host}]`);
54 return host;
55 }
56 });
57
58 1 return new globals.handlebars.SafeString(hosts.map(host => format(host)).join(''));
59 };
60 };
61
62 1 module.exports = [{
63 name: 'resourceHints',
64 factory: factory,
65 }];
66

helpers/setURLQueryParam.js

100%
25
25
0
Line Lint Hits Source
1 'use strict';
2 1 const utils = require('./3p/utils');
3 1 const common = require('./lib/common.js');
4 1 const { ValidationError } = require('../lib/errors');
5
6 1 const factory = globals => {
7 return function (string, key, value) {
8 string = common.unwrapIfSafeString(globals.handlebars, string);
9 18 key = common.unwrapIfSafeString(globals.handlebars, key);
10 18 value = common.unwrapIfSafeString(globals.handlebars, value);
11 18 if (!utils.isString(string) || !common.isValidURL(string)) {
12 6 throw new ValidationError("Invalid URL passed to setURLQueryParam");
13 12 } else if (!utils.isString(key)) {
14 1 throw new ValidationError("Invalid query parameter key passed to setURLQueryParam");
15 11 } else if (!utils.isString(value)) {
16 1 throw new ValidationError("Invalid query parameter value passed to setURLQueryParam");
17 }
18
19 10 const url = new URL(string);
20
21 10 url.searchParams.set(encodeURIComponent(key), encodeURIComponent(value));
22
23 10 return new globals.handlebars.SafeString(url.toString());
24 };
25 };
26
27 1 module.exports = [{
28 name: 'setURLQueryParam',
29 factory: factory,
30 }];
31

helpers/snippets.js

100%
10
10
0
Line Lint Hits Source
1 'use strict';
2
3 1 const factory = () => {
4 return function(location) {
5 return '<!-- snippet location ' + location + ' -->';
6 };
7 };
8
9 1 module.exports = [{
10 name: 'snippet',
11 factory: factory,
12 }];
13

helpers/strReplace.js

97.83%
46
45
1
Line Lint Hits Source
1 'use strict';
2 1 const common = require('./lib/common.js');
3 1 const { ValidationError } = require('../lib/errors');
4
5 1 const factory = globals => {
6 return function (str, substr, newSubstr, iteration) {
7 str = common.unwrapIfSafeString(globals.handlebars, str);
8 12 substr = common.unwrapIfSafeString(globals.handlebars, substr);
9 12 newSubstr = common.unwrapIfSafeString(globals.handlebars, newSubstr);
10 12 iteration = common.unwrapIfSafeString(globals.handlebars, iteration);
11
12 12 if (typeof str !== 'string') {
13 2 throw new ValidationError("Invalid query parameter string passed to strReplace");
14 10 } else if (typeof substr !== 'string') {
15 1 throw new ValidationError("Invalid query parameter substring passed to strReplace");
16 9 } else if (typeof newSubstr !== 'string') {
17 1 throw new ValidationError("Invalid query parameter new substring passed to strReplace");
18 }
19
20 8 if (typeof iteration !== 'number') {
21 3 return str.replace(new RegExp(escapeRegex(substr), 'g'), newSubstr);
22 }
23
24 5 const occurrence = getOccurrences(str, substr);
25
26 5 if (iteration > 0 && occurrence > 0) {
27 3 if (iteration >= occurrence) {
28 2 return str.replace(new RegExp(escapeRegex(substr), 'g'), newSubstr);
29 } else {
30 1 let result = str;
31 1 for (let i = 0; i < iteration; i++) {
32 2 result = result.replace(substr, newSubstr);
33 }
34 1 return result;
35 }
36 } else {
37 2 return str;
38 }
39 };
40 };
41
42 function escapeRegex(string) {
43 10 return string.replace(/[-\\^$*+?.()|[\]{}]/g, "\\$&");
44 }
45
46 function getOccurrences(str, substr) {
47 5 const matches = str.match(new RegExp(escapeRegex(substr), 'g'));
48
return
matches
? matches.length :
0
;
49 }
50
51 1 module.exports = [{
52 name: 'strReplace',
53 factory: factory,
54 }];
55

helpers/stripQuerystring.js

93.33%
15
14
1
Line Lint Hits Source
1 'use strict';
2 1 const utils = require('./3p/utils');
3 1 const common = require('./lib/common.js');
4
5 1 const factory = globals => {
6 return function (url) {
7 url = common.unwrapIfSafeString(globals.handlebars, url);
8
if (
utils.isString(url)
) {
9 3 return url.split('?')[0];
10 }
11 };
12 };
13
14 1 module.exports = [{
15 name: 'stripQuerystring',
16 factory: factory,
17 }];
18

helpers/stylesheet.js

94%
50
47
3
Line Lint Hits Source
1 'use strict';
2
3 1 const buildCDNHelper = require('./lib/cdnify');
4 1 const {
5 addResourceHint,
6 resourceHintAllowedTypes,
7 resourceHintAllowedCors,
8 defaultResourceHintState
9 } = require('./lib/resourceHints');
10 1 const utils = require('./3p/utils');
11
12 1 const factory = globals => {
13 const cdnify = buildCDNHelper(globals);
14
15 677 return function (assetPath) {
16 const siteSettings = globals.getSiteSettings();
17 7 const configId = siteSettings.theme_config_id;
18
19 7 const options = arguments[arguments.length - 1];
20
21 // append the configId only if the asset path starts with assets/css/
22 7 const path = configId && assetPath.match(/^\/?assets\/css\//)
23 ? assetPath.replace(/\.css$/, `-${configId}.css`)
24 : assetPath;
25
26 7 let url = cdnify(path);
27 7 if (utils.isString(url)) {
28 6 const cross = options.hash.crossorigin || resourceHintAllowedCors.noCors;
29 6 try {
30 6 const hintPath = addResourceHint(
31 globals,
32 url,
33 defaultResourceHintState,
34 resourceHintAllowedTypes.resourceHintStyleType,
35 cross
36 );
37
38
if (
utils.isString(hintPath)
) {
39 6 url = hintPath;
40 } else {
41 console.info(`Early hint generated and invalid path [${hintPath}]. stylesheet tag won't be using it.`);
42 }
43 } catch (e) {
44 console.info(`Early hint generation failed for path [${url}]`, e);
45 }
46 }
47
48 7 const attrs = Object.assign({rel: 'stylesheet'}, options.hash);
49 7 const keyValuePairs = [];
50 7 for (const attrsKey in attrs) {
51 11 keyValuePairs.push(`${attrsKey}="${attrs[attrsKey]}"`);
52 }
53
54 7 return `<link data-stencil-stylesheet href="${url}" ${keyValuePairs.join(' ')}>`;
55 };
56 };
57
58 1 module.exports = [{
59 name: 'stylesheet',
60 factory: factory,
61 }];
62

helpers/thirdParty.js

98.68%
152
150
2
Line Lint Hits Source
1 'use strict';
2
3 1 const whitelist = [
4 {
5 name: 'array',
6 include: [
7 'after',
8 'arrayify',
9 'before',
10 'eachIndex',
11 'filter',
12 'first',
13 'forEach',
14 'inArray',
15 'isArray',
16 'last',
17 'lengthEqual',
18 'map',
19 'some',
20 'sort',
21 'sortBy',
22 'withAfter',
23 'withBefore',
24 'withFirst',
25 'withLast',
26 'withSort',
27 ],
28 },
29 {
30 name: 'collection',
31 include: ['isEmpty', 'iterate', 'length'],
32 },
33 {
34 name: 'comparison',
35 module: require('./3p/comparison'),
36 include: [
37 'and',
38 'gt',
39 'gte',
40 'has',
41 'eq',
42 'ifEven',
43 'ifNth',
44 'ifOdd',
45 'is',
46 'isnt',
47 'lt',
48 'lte',
49 'neither',
50 'unlessEq',
51 'unlessGt',
52 'unlessLt',
53 'unlessGteq',
54 'unlessLteq',
55 ],
56 },
57 {
58 name: 'html',
59 include: ['ellipsis', 'sanitize', 'ul', 'ol', 'thumbnailImage']
60 },
61 {
62 name: 'inflection',
63 include: ['inflect', 'ordinalize'],
64 },
65 {
66 name: 'markdown',
67 include: ['markdown'],
68 },
69 {
70 name: 'math',
71 include: ['add', 'subtract', 'divide', 'multiply', 'floor', 'ceil', 'round', 'sum', 'avg'],
72 },
73 {
74 name: 'misc',
75 include: ['default', 'noop', 'withHash'],
76 },
77 {
78 name: 'number',
79 include: [
80 'addCommas',
81 'phoneNumber',
82 'random',
83 'toAbbr',
84 'toExponential',
85 'toFixed',
86 'toFloat',
87 'toInt',
88 'toPrecision',
89 ],
90 },
91 {
92 name: 'object',
93 include: [
94 'extend',
95 'forIn',
96 'forOwn',
97 'toPath',
98 'hasOwn',
99 'isObject',
100 'merge',
101 'JSONparse',
102 'JSONstringify',
103 ],
104 },
105 {
106 name: 'string',
107 module: require('./3p/string'),
108 include: [
109 'camelcase',
110 'capitalize',
111 'capitalizeAll',
112 'center',
113 'chop',
114 'dashcase',
115 'dotcase',
116 'hyphenate',
117 'isString',
118 'lowercase',
119 'pascalcase',
120 'pathcase',
121 'plusify',
122 'reverse',
123 'sentence',
124 'snakecase',
125 'split',
126 'startsWith',
127 'titleize',
128 'trim',
129 'uppercase'
130 ],
131 },
132 {
133 name: 'url',
134 include: ['encodeURI', 'decodeURI', 'urlResolve', 'urlParse', 'stripProtocol'],
135 },
136 ];
137
138
139 // Construct the data structure the caller expects: an array of { name, factory }
140 1 const exportedHelpers = [];
141 1 for (let i = 0; i < whitelist.length; i++) {
142 12 const spec = whitelist[i];
143
144 // Initialize module
145 12 const module = require(`./3p/${spec.name}`);
146
if (
typeof spec.init === 'function'
) {
147 spec.init(module);
148 }
149
150 // Pluck whitelisted functions from each helper module and wrap in object of expected format
151 12 const moduleWhitelist = spec.include;
152 12 for (let i = 0; i < moduleWhitelist.length; i++) {
153 105 const name = moduleWhitelist[i];
154 105 exportedHelpers.push({
155 name: name,
156 factory: () => module[name],
157 });
158 }
159 }
160
161 1 module.exports = exportedHelpers;
162

helpers/toLowerCase.js

100%
15
15
0
Line Lint Hits Source
1 'use strict';
2
3 1 const factory = () => {
4 return function(...args) {
5 args.pop();
6 6 const string = args[0];
7 6 if (typeof string !== 'string') {
8 4 return string;
9 }
10
11 2 return string.toLowerCase();
12 };
13 };
14
15 1 module.exports = [{
16 name: 'toLowerCase',
17 factory: factory,
18 }];
19

helpers/truncate.js

100%
17
17
0
Line Lint Hits Source
1 'use strict';
2
3 1 const substring = require('stringz').substring;
4
5 /* 2017-02-14
6 *
7 * FUNCTION
8 * truncate(str, length)
9 *
10 * DESCRIPTION (WHAT)
11 * Returns the first X characters in a string (unless it reaches the end
12 * of the string first, in which case it will return fewer). Returns a
13 * new string that is truncated to the given length.
14 *
15 * USE CASE (WHY)
16 * As a merchant, I want to display a card, at the bottom of
17 * my home page that highlights the most recent blog post. In the card,
18 * I want to display the blog thumbnail, title and the first 40 characters
19 * of the post's body. In order to extract the first 40 characters, I need
20 * a Handlebars helper that works like the javascript substring() function.
21 *
22 * USAGE
23 *
24 * {{lang (truncate 'blog.post.body.' 40) }}
25 */
26 1 const factory = globals => {
27 return function(...args) {
28 args.pop();
29 8 const [string, length] = args;
30 8 if (typeof string !== 'string' || string.length === 0) {
31 3 return string;
32 }
33
34 5 const truncatedString = substring(string, 0, length);
35
36 5 return new globals.handlebars.SafeString(truncatedString);
37 };
38 };
39
40 1 module.exports = [{
41 name: 'truncate',
42 factory: factory,
43 }];
44

helpers/typeof.js

90%
20
18
2
Line Lint Hits Source
1 'use strict';
2 1 const common = require('./lib/common.js');
3 1 const {ValidationError} = require("../lib/errors");
4
5 1 const factory = globals => {
6 return function (value) {
7 const options = arguments[arguments.length - 1];
8
if (
options.fn
) {
9 throw new ValidationError("Handlebars helper 'typeof' is not a block function.");
10 }
11 11 if (arguments.length-1 !== 1) {
12 1 throw new ValidationError("Handlebars helper 'typeof' requires exactly one argument. Provided: " + (arguments.length-1));
13 }
14 // Possible future issue due to type conversion: future implementation of flag to disable unwrap may be advisable.
15 // TODO: Add flag to disable unwrap
16 10 const toTest = common.unwrapIfSafeString(globals.handlebars, value);
17 10 return typeof toTest;
18 };
19 };
20
21 1 module.exports = [{
22 name: 'typeof',
23 factory: factory,
24 }];
25

helpers/unless.js

100%
16
16
0
Line Lint Hits Source
1 'use strict';
2
3 1 const factory = globals => {
4 return function() {
5 const options = arguments[arguments.length - 1];
6 10 arguments[arguments.length - 1] = Object.assign({}, options, {
7 fn: options.inverse || (() => false),
8 inverse: options.fn || (() => true),
9 hash: options.hash
10 });
11
12 10 return globals.handlebars.helpers['if'].apply(this, arguments);
13 };
14 };
15
16 1 module.exports = [{
17 name: 'unless',
18 factory: factory,
19 }];
20

helpers/3p/array.js

98.04%
255
250
5
Line Lint Hits Source
1 'use strict';
2
3 1 var utils = require('./utils');
4
5 /**
6 * Expose `helpers`
7 */
8
9 1 var helpers = module.exports;
10
11 /**
12 * Returns all of the items in an array after the specified index.
13 * Opposite of [before](#before).
14 *
15 * Given the array `['a', 'b', 'c']`:
16 *
17 * ```handlebars
18 * {{after array 1}}
19 * //=> '["c"]'
20 * ```
21 *
22 * @param {Array} `array` Collection
23 * @param {Number} `n` Starting index (number of items to exclude)
24 * @return {Array} Array exluding `n` items.
25 * @api public
26 */
27
28 1 helpers.after = function(array, n) {
29 2 if (!array || utils.isUndefined(array)) {return '';}
30 3 return array.slice(n);
31 };
32
33 /**
34 * Cast the given `value` to an array.
35 *
36 * ```handlebars
37 * {{arrayify "foo"}}
38 * //=> '["foo"]'
39 * ```
40 * @param {any} `value`
41 * @return {Array}
42 * @api public
43 */
44
45 1 helpers.arrayify = function(...args) {
46 args.pop(); // remove handlebars options object
47 5 const value = args[0];
48 5 return value ? (Array.isArray(value) ? value : [value]) : [];
49 };
50
51 /**
52 * Return all of the items in the collection before the specified
53 * count. Opposite of [after](#after).
54 *
55 * Given the array `['a', 'b', 'c']`:
56 *
57 * ```handlebars
58 * {{before array 2}}
59 * //=> '["a", "b"]'
60 * ```
61 *
62 * @param {Array} `array`
63 * @param {Number} `n`
64 * @return {Array} Array excluding items after the given number.
65 * @api public
66 */
67
68 1 helpers.before = function(array, n) {
69 1 if (utils.isUndefined(array)) {return '';}
70 2 return array.slice(0, -n);
71 };
72
73 /**
74 * ```handlebars
75 * {{#eachIndex collection}}
76 * {{item}} is {{index}}
77 * {{/eachIndex}}
78 * ```
79 * @param {Array} `array`
80 * @return {String}
81 * @block
82 * @api public
83 */
84
85 1 helpers.eachIndex = function(array) {
86 var result = '';
87 2 if (Array.isArray(array)) {
88 1 const options = arguments[arguments.length - 1];
89 1 for (var i = 0; i < array.length; i++) {
90 8 result += options.fn({item: array[i], index: i});
91 }
92 }
93 2 return result;
94 };
95
96 /**
97 * Block helper that filters the given array and renders the block for values that
98 * evaluate to `true`, otherwise the inverse block is returned.
99 *
100 * Given the array `['a', 'b', 'c']`:
101 *
102 * ```handlebars
103 * {{#filter array "foo"}}AAA{{else}}BBB{{/filter}}
104 * //=> 'BBB'
105 * ```
106 *
107 * @name .filter
108 * @param {Array} `array`
109 * @param {any} `value`
110 * @return {String}
111 * @block
112 * @api public
113 */
114
115 1 helpers.filter = function(array, value) {
116 var content = '';
117 3 var results = [];
118 3 const options = arguments[arguments.length - 1];
119
120 // filter on a specific property
121
var prop =
options.hash
&& options.hash.property;
122 3 if (prop) {
123 1 results = utils.filter(array, function(val) {
124 return utils.get(val, prop) === value;
125 });
126 } else {
127
128 // filter on a string value
129 2 results = utils.filter(array, function(v) {
130 return value === v;
131 });
132 }
133
134
if (
results
&& results.length > 0) {
135 2 for (var i = 0; i < results.length; i++) {
136 2 content += options.fn(results[i]);
137 }
138 2 return content;
139 }
140 1 return options.inverse(this);
141 };
142
143 /**
144 * Returns the first item, or first `n` items of an array.
145 *
146 * Given the array `['a', 'b', 'c', 'd', 'e']`:
147 *
148 * ```handlebars
149 * {{first array 2}}
150 * //=> '["a", "b"]'
151 * ```
152 *
153 * @param {Array} `array`
154 * @param {Number} `n` Number of items to return, starting at `0`.
155 * @return {Array}
156 * @api public
157 */
158
159 1 helpers.first = function(array, n) {
160 if (Array.isArray(array)) {
161 9 return arrayAlt();
162 }
163
164 9 if (utils.isString(array)) {
165 5 const chars = array.split('');
166 5 const result = arrayAlt(chars, n);
167
return
Array.isArray(result)
?
result.join('')
: result;
168 }
169
170 4 return [];
171
172 function arrayAlt() {
173
if (
utils.isUndefined(array)
) {return '';}
174 14 if (!utils.isNumber(n)) {
175 4 return array[0];
176 }
177 10 return array.slice(0, n);
178 }
179 };
180
181 /**
182 * Iterates over each item in an array and exposes the current item
183 * in the array as context to the inner block. In addition to
184 * the current array item, the helper exposes the following variables
185 * to the inner block:
186 *
187 * - `index`
188 * - `total`
189 * - `isFirst`
190 * - `isLast`
191 *
192 * Also, `@index` is exposed as a private variable, and additional
193 * private variables may be defined as hash arguments.
194 *
195 * ```js
196 * var accounts = [
197 * {'name': 'John', 'email': 'john@example.com'},
198 * {'name': 'Malcolm', 'email': 'malcolm@example.com'},
199 * {'name': 'David', 'email': 'david@example.com'}
200 * ];
201 *
202 * // example usage
203 * // {{#forEach accounts}}
204 * // <a href="mailto:{{ email }}" title="Send an email to {{ name }}">
205 * // {{ name }}
206 * // </a>{{#unless isLast}}, {{/unless}}
207 * // {{/forEach}}
208 * ```
209 * @source <http://stackoverflow.com/questions/13861007>
210 * @param {Array} `array`
211 * @return {String}
212 * @block
213 * @api public
214 */
215
216 1 helpers.forEach = function(array) {
217 const options = arguments[arguments.length - 1];
218 9 var data = utils.createFrame(options, options.hash);
219 9 if (!array) {
220 1 return '';
221 }
222 8 var len = array.length;
223 8 var buffer = '';
224 8 var i = -1;
225
226 8 while (++i < len) {
227 21 var item = array[i];
228 21 data.index = i;
229 21 item.index = i + 1;
230 21 item.total = len;
231 21 item.isFirst = i === 0;
232 21 item.isLast = i === (len - 1);
233 21 buffer += options.fn(item, {data: data});
234 }
235 8 return buffer;
236 };
237
238 /**
239 * Block helper that renders the block if an array has the
240 * given `value`. Optionally specify an inverse block to render
241 * when the array does not have the given value.
242 *
243 * Given the array `['a', 'b', 'c']`:
244 *
245 * ```handlebars
246 * {{#inArray array "d"}}
247 * foo
248 * {{else}}
249 * bar
250 * {{/inArray}}
251 * //=> 'bar'
252 * ```
253 *
254 * @name .inArray
255 * @param {Array} `array`
256 * @param {any} `value`
257 * @return {String}
258 * @block
259 * @api public
260 */
261
262 1 helpers.inArray = function(array, value) {
263 const options = arguments[arguments.length - 1];
264 2 if (utils.indexOf(array, value) > -1) {
265 1 return options.fn(this);
266 }
267 1 return options.inverse(this);
268 };
269
270 /**
271 * Returns true if `value` is an es5 array.
272 *
273 * ```handlebars
274 * {{isArray "abc"}}
275 * //=> 'false'
276 * ```
277 *
278 * @param {any} `value` The value to test.
279 * @return {Boolean}
280 * @api public
281 */
282
283 1 helpers.isArray = function(value) {
284 return Array.isArray(value);
285 };
286
287
288 /**
289 * Returns the last item, or last `n` items of an array.
290 * Opposite of [first](#first).
291 *
292 * Given the array `['a', 'b', 'c', 'd', 'e']`:
293 *
294 * ```handlebars
295 * {{last array 2}}
296 * //=> '["d", "e"]'
297 * ```
298 * @param {Array} `array`
299 * @param {Number} `n` Number of items to return, starting with the last item.
300 * @return {Array}
301 * @api public
302 */
303
304 1 helpers.last = function(array, n) {
305
306 function arrayAlt(array, n) {
307 11 if (!utils.isNumber(n)) {
308 3 return array[array.length - 1];
309 }
310 8 return array.slice(-n);
311 }
312
313 function stringAlt(str, n) {
314 5 const chars = str.split('');
315 5 const result = arrayAlt(chars, n);
316 5 return Array.isArray(result) ? result.join('') : result;
317 }
318
319 16 if (Array.isArray(array)) {
320 6 return arrayAlt(array, n);
321 }
322
323 10 if (utils.isString(array)) {
324 5 return stringAlt(array, n);
325 }
326
327 5 return [];
328 };
329
330 /**
331 * Block helper that compares the length of the given array to
332 * the number passed as the second argument. If the array length
333 * is equal to the given `length`, the block is returned,
334 * otherwise an inverse block may optionally be returned.
335 *
336 * Given the array `['a', 'b', 'c', 'd', 'e']`:
337 *
338 * ```handlebars
339 * {{#lengthEqual array 10}}AAA{{else}}BBB{{/lengthEqual}}
340 * //=> 'BBB'
341 * ```
342 *
343 * @name .lengthEqual
344 * @param {Array} `array`
345 * @param {Number} `length`
346 * @return {String}
347 * @block
348 * @api public
349 */
350
351 1 helpers.lengthEqual = function(array, length) {
352 const options = arguments[arguments.length - 1];
353 2 if (array.length === length) {
354 1 return options.fn(this);
355 }
356 1 return options.inverse(this);
357 };
358
359 /**
360 * Returns a new array, created by calling `function`
361 * on each element of the given `array`.
362 *
363 * Given an array `['a', 'b', 'c']`:
364 *
365 * ```js
366 * // register `double` as a helper
367 * function double(str) {
368 * return str + str;
369 * }
370 * // then used like this:
371 * // {{map array double}}
372 * //=> '["aa", "bb", "cc"]'
373 * ```
374 *
375 * @param {Array} `array`
376 * @param {Function} `fn`
377 * @return {String}
378 * @api public
379 */
380
381 1 helpers.map = function(array, fn) {
382 1 if (utils.isUndefined(array)) {return '';}
383 3 if (typeof array === 'string' && /[[]/.test(array)) {
384 2 array = utils.tryParse(array) || [];
385 }
386 3 var len = array.length;
387 3 var res = new Array(len);
388 3 var i = -1;
389
390 3 while (++i < len) {
391 6 res[i] = fn(array[i], i, array);
392 }
393 3 return res;
394 };
395
396 /**
397 * Block helper that returns the block if the callback returns true
398 * for some value in the given array.
399 *
400 * Given the array `[1, 'b', 3]`:
401 *
402 * ```handlebars
403 * {{#some array isString}}
404 * Render me if the array has a string.
405 * {{else}}
406 * Render me if it doesn't.
407 * {{/some}}
408 * //=> 'Render me if the array has a string.'
409 * ```
410 * @name .some
411 * @param {Array} `array`
412 * @param {Function} `cb` callback function
413 * @return {Array}
414 * @block
415 * @api public
416 */
417
418 1 helpers.some = function(arr, cb) {
419 const options = arguments[arguments.length - 1];
420 3 cb = utils.iterator(cb, this);
421 3 if (!arr) {
422 1 return options.inverse(this);
423 }
424 2 var len = arr.length, i = -1;
425 2 while (++i < len) {
426 4 if (cb(arr[i], i, arr)) {
427 1 return options.fn(this);
428 }
429 }
430 1 return options.inverse(this);
431 };
432
433 /**
434 * Sort the given `array`. If an array of objects is passed,
435 * you may optionally pass a `key` to sort on as the second
436 * argument. You may alternatively pass a sorting function as
437 * the second argument.
438 *
439 * Given an array `['b', 'a', 'c']`:
440 *
441 * ```handlebars
442 * {{sort array}}
443 * //=> '["a", "b", "c"]'
444 * ```
445 *
446 * @param {Array} `array` the array to sort.
447 * @param {String|Function} `key` The object key to sort by, or sorting function.
448 * @api public
449 */
450
451 1 helpers.sort = function(arr, options) {
452 1 if (utils.isUndefined(arr)) {return '';}
453 3 if (utils.get(options, 'hash.reverse')) {
454 1 return arr.sort().reverse();
455 }
456 2 return arr.sort();
457 };
458
459 /**
460 * Sort an `array`. If an array of objects is passed,
461 * you may optionally pass a `key` to sort on as the second
462 * argument. You may alternatively pass a sorting function as
463 * the second argument.
464 *
465 * Given an array `[{a: 'zzz'}, {a: 'aaa'}]`:
466 *
467 * ```handlebars
468 * {{sortBy array "a"}}
469 * //=> '[{"a":"aaa"}, {"a":"zzz"}]'
470 * ```
471 *
472 * @param {Array} `array` the array to sort.
473 * @param {String|Function} `props` One or more properties to sort by, or sorting functions to use.
474 * @api public
475 */
476
477 1 helpers.sortBy = function(arr/*, prop*/) {
478 1 if (utils.isUndefined(arr)) {return '';}
479 4 var args = [].slice.call(arguments);
480 4 args.pop(); // remove hbs options object
481
482 4 if (typeof args[0] === 'string' && /[[]/.test(args[0])) {
483 3 args[0] = utils.tryParse(args[0]) || [];
484 }
485 4 if (utils.isUndefined(args[1])) {
486 2 return args[0].sort();
487 }
488 2 return utils.sortBy.apply(null, args);
489 };
490
491 /**
492 * Use the items in the array _after_ the specified index
493 * as context inside a block. Opposite of [withBefore](#withBefore).
494 *
495 * Given the array `['a', 'b', 'c', 'd', 'e']`:
496 *
497 * ```handlebars
498 * {{#withAfter array 3}}
499 * {{this}}
500 * {{/withAfter}}
501 * //=> "de"
502 * ```
503 * @param {Array} `array`
504 * @param {Number} `idx`
505 * @return {Array}
506 * @block
507 * @api public
508 */
509
510 1 helpers.withAfter = function(array, idx) {
511 const options = arguments[arguments.length - 1];
512 1 array = array.slice(idx);
513 1 var result = '';
514
515 1 var len = array.length, i = -1;
516 1 while (++i < len) {
517 3 result += options.fn(array[i]);
518 }
519 1 return result;
520 };
521
522 /**
523 * Use the items in the array _before_ the specified index
524 * as context inside a block. Opposite of [withAfter](#withAfter).
525 *
526 * Given the array `['a', 'b', 'c', 'd', 'e']`:
527 *
528 * ```handlebars
529 * {{#withBefore array 3}}
530 * {{this}}
531 * {{/withBefore}}
532 * //=> 'ab'
533 * ```
534 * @param {Array} `array`
535 * @param {Number} `idx`
536 * @return {Array}
537 * @block
538 * @api public
539 */
540
541 1 helpers.withBefore = function(array, idx) {
542 const options = arguments[arguments.length - 1];
543 1 array = array.slice(0, -idx);
544 1 var result = '';
545
546 1 var len = array.length, i = -1;
547 1 while (++i < len) {
548 3 result += options.fn(array[i]);
549 }
550 1 return result;
551 };
552
553 /**
554 * Use the first item in a collection inside a handlebars
555 * block expression. Opposite of [withLast](#withLast).
556 *
557 * Given the array `['a', 'b', 'c']`:
558 *
559 * ```handlebars
560 * {{#withFirst array}}
561 * {{this}}
562 * {{/withFirst}}
563 * //=> 'a'
564 * ```
565 * @param {Array} `array`
566 * @param {Number} `idx`
567 * @return {String}
568 * @block
569 * @api public
570 */
571
572 1 helpers.withFirst = function(arr, idx) {
573 const options = arguments[arguments.length - 1];
574 4 if (utils.isEmpty(arr) || utils.isUndefined(arr)) {
575 2 return '';
576 }
577 2 arr = utils.result(arr);
578
579 2 if (!utils.isUndefined(idx)) {
580 1 idx = parseFloat(utils.result(idx));
581 }
582
583 2 if (utils.isUndefined(idx)) {
584 1 return options.fn(arr[0]);
585 }
586
587 1 arr = arr.slice(0, idx);
588 1 var len = arr.length, i = -1;
589 1 var result = '';
590 1 while (++i < len) {
591 2 result += options.fn(arr[i]);
592 }
593 1 return result;
594 };
595
596 /**
597 * Use the last item or `n` items in an array as context inside a block.
598 * Opposite of [withFirst](#withFirst).
599 *
600 * Given the array `['a', 'b', 'c']`:
601 *
602 * ```handlebars
603 * {{#withLast array}}
604 * {{this}}
605 * {{/withLast}}
606 * //=> 'c'
607 * ```
608 * @param {Array} `array`
609 * @param {Number} `idx` The starting index.
610 * @return {String}
611 * @block
612 * @api public
613 */
614
615 1 helpers.withLast = function(array, idx) {
616 1 if (utils.isUndefined(array)) {return '';}
617 2 const options = arguments[arguments.length - 1];
618 2 array = utils.result(array);
619
620 2 if (!utils.isUndefined(idx)) {
621 1 idx = parseFloat(utils.result(idx));
622 }
623
624 2 if (utils.isUndefined(idx)) {
625 1 return options.fn(array[array.length - 1]);
626 }
627
628 1 array = array.slice(-idx);
629 1 var len = array.length, i = -1;
630 1 var result = '';
631 1 while (++i < len) {
632 2 result += options.fn(array[i]);
633 }
634 1 return result;
635 };
636
637 /**
638 * Block helper that sorts a collection and exposes the sorted
639 * collection as context inside the block.
640 *
641 * Given the array `['b', 'a', 'c']`:
642 *
643 * ```handlebars
644 * {{#withSort array}}{{this}}{{/withSort}}
645 * //=> 'abc'
646 * ```
647 * @name .withSort
648 * @param {Array} `array`
649 * @param {String} `prop`
650 * @return {String}
651 * @block
652 * @api public
653 */
654
655 1 helpers.withSort = function(array, prop) {
656 1 if (utils.isUndefined(array)) {return '';}
657 4 const options = arguments[arguments.length - 1];
658 4 var result = '';
659
660 4 if (utils.isUndefined(prop)) {
661 2 array = array.sort();
662 2 if (utils.get(options, 'hash.reverse')) {
663 1 array = array.reverse();
664 }
665
666 2 for (var i = 0, len = array.length; i < len; i++) {
667 16 result += options.fn(array[i]);
668 }
669 2 return result;
670 }
671
672 2 array.sort(function(a, b) {
673 a = utils.get(a, prop);
674 4 b = utils.get(b, prop);
675
return
a > b
?
1
: (
a < b
? -1 :
0
);
676 });
677
678 2 if (utils.get(options, 'hash.reverse')) {
679 1 array = array.reverse();
680 }
681
682 2 var alen = array.length, j = -1;
683 2 while (++j < alen) {
684 6 result += options.fn(array[j]);
685 }
686 2 return result;
687 };

helpers/3p/collection.js

97.50%
40
39
1
Line Lint Hits Source
1 'use strict';
2
3 1 var array = require('./array');
4 1 var object = require('./object');
5 1 var utils = require('./utils');
6 1 var forEach = array.forEach;
7 1 var forOwn = object.forOwn;
8
9 /**
10 * Expose `helpers`
11 */
12
13 1 var helpers = module.exports;
14
15 /**
16 * Block helper that returns a block if the given collection is
17 * empty. If the collection is not empty the inverse block is returned
18 * (if supplied).
19 *
20 * @name .isEmpty
21 * @param {Object} `collection`
22 * @return {String}
23 * @block
24 * @api public
25 */
26
27 1 helpers.isEmpty = function(collection) {
28 const options = arguments[arguments.length - 1];
29 5 if (utils.isOptions(collection)) {
30 1 return collection.fn(this);
31 }
32
33 4 if (Array.isArray(collection) && !collection.length) {
34 1 return options.fn(this);
35 }
36
37 3 var keys = Object.keys(collection);
38
if (
typeof collection === 'object'
&& !keys.length) {
39 1 return options.fn(this);
40 }
41 2 return options.inverse(this);
42 };
43
44 /**
45 * Iterate over an array or object,
46 *
47 * @name .iterate
48 * @param {Object|Array} `context` The collection to iterate over
49 * @return {String}
50 * @block
51 * @api public
52 */
53
54 1 helpers.iterate = function(context) {
55 const options = arguments[arguments.length - 1];
56 5 if (Array.isArray(context)) {
57 2 return forEach.apply(forEach, arguments);
58 3 } else if (utils.isObject(context)) {
59 2 return forOwn.apply(forOwn, arguments);
60 }
61 1 return options.inverse(this);
62 };
63
64 /**
65 * Returns the length of the given collection. When using a string literal in the
66 * template, the string must be value JSON. See the example below. Otherwise pass
67 * in an array or object from the context
68 *
69 * ```handlebars
70 * {{length '["a", "b", "c"]'}}
71 * //=> 3
72 *
73 * //=> myArray = ['a', 'b', 'c', 'd', 'e'];
74 * {{length myArray}}
75 * //=> 5
76 *
77 * //=> myObject = {'a': 'a', 'b': 'b'};
78 * {{length myObject}}
79 * //=> 2
80 * ```
81 * @param {Array|Object|String} `value`
82 * @return {Number} The length of the value.
83 * @api public
84 */
85
86 1 helpers.length = function(value) {
87 2 if (!value || utils.isUndefined(value)) {return '';}
88 6 if (typeof value === 'string' && /[[]/.test(value)) {
89 2 value = utils.tryParse(value) || [];
90 }
91 6 if (utils.isObject(value)) {
92 1 value = Object.keys(value);
93 }
94 6 return value.length;
95 };

helpers/3p/comparison.js

98.14%
161
158
3
Line Lint Hits Source
1 'use strict';
2
3 1 var isObject = require('./object').isObject;
4 1 var isString = require('./string').isString;
5 1 var utils = require('./utils');
6
7 /**
8 * Expose `helpers`
9 */
10
11 1 var helpers = module.exports;
12
13 /**
14 * Block helper that renders the block if **both** of the given values
15 * are truthy. If an inverse block is specified it will be rendered
16 * when falsy.
17 *
18 * @param {any} `a`
19 * @param {any} `b`
20 * @return {String}
21 * @block
22 * @api public
23 */
24
25 1 helpers.and = function(a, b) {
26 const options = arguments[arguments.length - 1];
27
if (
a
&& b) {
28 1 return options.fn(this);
29 }
30 1 return options.inverse(this);
31 };
32
33 /**
34 * Block helper that renders a block if `a` is **greater than** `b`.
35 *
36 * If an inverse block is specified it will be rendered when falsy.
37 * You may optionally use the `compare=""` hash argument for the
38 * second value.
39 *
40 * @name .gt
41 * @param {String} `a`
42 * @param {String} `b`
43 * @return {String} Block, or inverse block if specified and falsey.
44 * @block
45 * @api public
46 */
47
48 1 helpers.gt = function(a, b) {
49 const options = arguments[arguments.length - 1];
50 6 if (arguments.length === 2) {
51 3 b = options.hash.compare;
52 }
53 6 if (a > b) {
54 2 return options.fn(this);
55 }
56 4 return options.inverse(this);
57 };
58
59 /**
60 * Block helper that renders a block if `a` is **greater than or
61 * equal to** `b`.
62 *
63 * If an inverse block is specified it will be rendered when falsy.
64 * You may optionally use the `compare=""` hash argument for the
65 * second value.
66 *
67 * @name .gte
68 * @param {String} `a`
69 * @param {String} `b`
70 * @return {String} Block, or inverse block if specified and falsey.
71 * @block
72 * @api public
73 */
74
75 1 helpers.gte = function(a, b) {
76 const options = arguments[arguments.length - 1];
77 6 if (arguments.length === 2) {
78 3 b = options.hash.compare;
79 }
80 6 if (a >= b) {
81 4 return options.fn(this);
82 }
83 2 return options.inverse(this);
84 };
85
86 /**
87 * Block helper that renders a block if `value` has `pattern`.
88 * If an inverse block is specified it will be rendered when falsy.
89 *
90 * @param {any} `val` The value to check.
91 * @param {any} `pattern` The pattern to check for.
92 * @return {String}
93 * @block
94 * @api public
95 */
96
97 1 helpers.has = function(value, pattern) {
98 const options = arguments[arguments.length - 1];
99 8 if (arguments.length === 2) {
100 1 return pattern.inverse(this);
101 }
102
103 7 if (arguments.length === 1) {
104 1 return value.inverse(this);
105 }
106
107 6 if ((Array.isArray(value) || isString(value)) && isString(pattern)) {
108 5 if (value.indexOf(pattern) > -1) {
109 3 return options.fn(this);
110 }
111 }
112
if (isObject(value) &&
isString(pattern)
&& pattern in value) {
113 1 return options.fn(this);
114 }
115 2 return options.inverse(this);
116 };
117
118 /**
119 * Block helper that renders a block if `a` is **equal to** `b`.
120 * If an inverse block is specified it will be rendered when falsy.
121 * You may optionally use the `compare=""` hash argument for the
122 * second value.
123 *
124 * @name .eq
125 * @param {String} `a`
126 * @param {String} `b`
127 * @return {String} Block, or inverse block if specified and falsey.
128 * @block
129 * @api public
130 */
131
132 1 helpers.eq = function(a, b) {
133 const options = arguments[arguments.length - 1];
134 3 if (arguments.length === 2) {
135 2 b = options.hash.compare;
136 }
137 3 if (a === b) {
138 1 return options.fn(this);
139 }
140 2 return options.inverse(this);
141 };
142
143 /**
144 * Return true if the given value is an even number.
145 *
146 * ```handlebars
147 * {{#ifEven value}}
148 * render A
149 * {{else}}
150 * render B
151 * {{/ifEven}}
152 * ```
153 * @param {Number} `number`
154 * @return {String} Block, or inverse block if specified and falsey.
155 * @block
156 * @api public
157 */
158
159 1 helpers.ifEven = function(num) {
160 const options = arguments[arguments.length - 1];
161 2 return utils.isEven(num)
162 ? options.fn(this)
163 : options.inverse(this);
164 };
165
166 /**
167 * Conditionally renders a block if the remainder is zero when
168 * `a` operand is divided by `b`. If an inverse block is specified
169 * it will be rendered when the remainder is **not zero**.
170 *
171 * @param {Number}
172 * @param {Number}
173 * @return {String} Block, or inverse block if specified and falsey.
174 * @block
175 * @api public
176 */
177
178 1 helpers.ifNth = function(a, b) {
179 const options = arguments[arguments.length - 1];
180
if (
utils.isNumber(a)
&&
utils.isNumber(b)
&& b % a === 0) {
181 3 return options.fn(this);
182 }
183 2 return options.inverse(this);
184 };
185
186 /**
187 * Block helper that renders a block if `value` is **an odd number**.
188 * If an inverse block is specified it will be rendered when falsy.
189 *
190 * ```handlebars
191 * {{#ifOdd value}}
192 * render A
193 * {{else}}
194 * render B
195 * {{/ifOdd}}
196 * ```
197 * @param {Object} `value`
198 * @return {String} Block, or inverse block if specified and falsey.
199 * @block
200 * @api public
201 */
202
203 1 helpers.ifOdd = function(val) {
204 const options = arguments[arguments.length - 1];
205 2 return utils.isOdd(val)
206 ? options.fn(this)
207 : options.inverse(this);
208 };
209
210 /**
211 * Block helper that renders a block if `a` is **equal to** `b`.
212 * If an inverse block is specified it will be rendered when falsy.
213 *
214 * @name .is
215 * @param {any} `a`
216 * @param {any} `b`
217 * @return {String}
218 * @block
219 * @api public
220 */
221
222 1 helpers.is = function(a, b) {
223 const options = arguments[arguments.length - 1];
224 3 if (arguments.length === 2) {
225 1 b = options.hash.compare;
226 }
227 3 if (a === b) {
228 2 return options.fn(this);
229 }
230 1 return options.inverse(this);
231 };
232
233 /**
234 * Block helper that renders a block if `a` is **not equal to** `b`.
235 * If an inverse block is specified it will be rendered when falsy.
236 *
237 * @name .isnt
238 * @param {String} `a`
239 * @param {String} `b`
240 * @return {String}
241 * @block
242 * @api public
243 */
244
245 1 helpers.isnt = function(a, b) {
246 const options = arguments[arguments.length - 1];
247 3 if (arguments.length === 2) {
248 1 b = options.hash.compare;
249 }
250 3 if (a !== b) {
251 2 return options.fn(this);
252 }
253 1 return options.inverse(this);
254 };
255
256 /**
257 * Block helper that renders a block if `a` is **less than** `b`.
258 *
259 * If an inverse block is specified it will be rendered when falsy.
260 * You may optionally use the `compare=""` hash argument for the
261 * second value.
262 *
263 * @name .lt
264 * @param {Object} `context`
265 * @return {String} Block, or inverse block if specified and falsey.
266 * @block
267 * @api public
268 */
269
270 1 helpers.lt = function(a, b) {
271 const options = arguments[arguments.length - 1];
272 5 if (arguments.length === 2) {
273 2 b = options.hash.compare;
274 }
275 5 if (a < b) {
276 2 return options.fn(this);
277 }
278 3 return options.inverse(this);
279 };
280
281 /**
282 * Block helper that renders a block if `a` is **less than or
283 * equal to** `b`.
284 *
285 * If an inverse block is specified it will be rendered when falsy.
286 * You may optionally use the `compare=""` hash argument for the
287 * second value.
288 *
289 * @name .lte
290 * @param {Sring} `a`
291 * @param {Sring} `b`
292 * @return {String} Block, or inverse block if specified and falsey.
293 * @block
294 * @api public
295 */
296
297 1 helpers.lte = function(a, b) {
298 const options = arguments[arguments.length - 1];
299 6 if (arguments.length === 2) {
300 3 b = options.hash.compare;
301 }
302 6 if (a <= b) {
303 4 return options.fn(this);
304 }
305 2 return options.inverse(this);
306 };
307
308 /**
309 * Block helper that renders a block if **neither of** the given values
310 * are truthy. If an inverse block is specified it will be rendered
311 * when falsy.
312 *
313 * @name .neither
314 * @param {any} `a`
315 * @param {any} `b`
316 * @return {String} Block, or inverse block if specified and falsey.
317 * @block
318 * @api public
319 */
320
321 1 helpers.neither = function(a, b) {
322 const options = arguments[arguments.length - 1];
323 2 if (!a && !b) {
324 1 return options.fn(this);
325 }
326 1 return options.inverse(this);
327 };
328
329
330 /**
331 * Block helper that always renders the inverse block **unless `a` is
332 * is equal to `b`**.
333 *
334 * @name .unlessEq
335 * @param {String} `a`
336 * @param {String} `b`
337 * @return {String} Inverse block by default, or block if falsey.
338 * @block
339 * @api public
340 */
341
342 1 helpers.unlessEq = function(context) {
343 const options = arguments[arguments.length - 1];
344 2 if (context === options.hash.compare) {
345 1 return options.inverse(this);
346 }
347 1 return options.fn(this);
348 };
349
350 /**
351 * Block helper that always renders the inverse block **unless `a` is
352 * is greater than `b`**.
353 *
354 * @name .unlessGt
355 * @param {Object} `context`
356 * @return {String} Inverse block by default, or block if falsey.
357 * @block
358 * @api public
359 */
360
361 1 helpers.unlessGt = function(context) {
362 const options = arguments[arguments.length - 1];
363 2 if (context > options.hash.compare) {
364 1 return options.inverse(this);
365 }
366 1 return options.fn(this);
367 };
368
369 /**
370 * Block helper that always renders the inverse block **unless `a` is
371 * is less than `b`**.
372 *
373 * @name .unlessLt
374 * @param {Object} `context`
375 * @return {String} Block, or inverse block if specified and falsey.
376 * @block
377 * @api public
378 */
379
380 1 helpers.unlessLt = function(context) {
381 const options = arguments[arguments.length - 1];
382 2 if (context < options.hash.compare) {
383 1 return options.inverse(this);
384 }
385 1 return options.fn(this);
386 };
387
388 /**
389 * Block helper that always renders the inverse block **unless `a` is
390 * is greater than or equal to `b`**.
391 *
392 * @name .unlessGteq
393 * @param {Object} `context`
394 * @return {String} Block, or inverse block if specified and falsey.
395 * @block
396 * @api public
397 */
398
399 1 helpers.unlessGteq = function(context) {
400 const options = arguments[arguments.length - 1];
401 3 if (context >= options.hash.compare) {
402 2 return options.inverse(this);
403 }
404 1 return options.fn(this);
405 };
406
407 /**
408 * Block helper that always renders the inverse block **unless `a` is
409 * is less than or equal to `b`**.
410 *
411 * @name .unlessLteq
412 * @param {Object} `context`
413 * @return {String} Block, or inverse block if specified and falsey.
414 * @block
415 * @api public
416 */
417
418 1 helpers.unlessLteq = function(context) {
419 const options = arguments[arguments.length - 1];
420 3 if (context <= options.hash.compare) {
421 2 return options.inverse(this);
422 }
423 1 return options.fn(this);
424 };

helpers/3p/html.js

91.95%
87
80
7
Line Lint Hits Source
1 'use strict';
2
3 1 const utils = require('./utils');
4 1 const striptags = require('./utils/lib/striptags');
5
6 function parseAttributes(hash) {
7 21 return Object.keys(hash).map(function (key) {
8 return key + '="' + hash[key] + '"';
9 }).join(' ');
10 };
11
12 function sanitize(str) {
13 5 if (!utils.isString(str)) { return ''; }
14 3 return striptags(str).trim();
15 };
16
17 /**
18 * Truncate a string by removing all HTML tags and limiting the result
19 * to the specified `length`. Aslo see [ellipsis](#ellipsis).
20 *
21 * ```js
22 * truncate("<span>foo bar baz</span>", 7);
23 * //=> 'foo bar'
24 * ```
25 *
26 * @name .truncate
27 * @param {String} `str`
28 * @param {Number} `limit` The desired length of the returned string.
29 * @param {String} `suffix` Optionally supply a string to use as a suffix to
30 * denote when the string has been truncated.
31 * @return {String} The truncated string.
32 * @api public
33 */
34 function truncate(str, limit, suffix) {
35
if (
str
&& typeof str === 'string') {
36
var ch =
typeof suffix === 'string'
?
suffix
: '';
37
if (
str.length > limit
) {
38 2 return sanitize(str).slice(0, limit - ch.length) + ch;
39 }
40 return str;
41 }
42 };
43
44 /**
45 * Expose `helpers`
46 */
47
48 1 var helpers = module.exports;
49
50 /**
51 * Truncates a string to the specified `length`, and appends
52 * it with an elipsis, `…`.
53 *
54 * ```js
55 * {{ellipsis "<span>foo bar baz</span>", 7}}
56 * //=> 'foo bar…'
57 * ```
58 * @name .ellipsis
59 * @param {String} `str`
60 * @param {Number} `length` The desired length of the returned string.
61 * @return {String} The truncated string.
62 * @api public
63 */
64
65 1 helpers.ellipsis = function (str, limit) {
66
if (
str
&& typeof str === 'string') {
67 3 if (str.length <= limit) {
68 1 return str;
69 }
70 2 return truncate(str, limit) + '…';
71 }
72 };
73
74
75 /**
76 * Strip HTML tags from a string, so that only the text nodes
77 * are preserved.
78 *
79 * ```js
80 * {{sanitize "<span>foo</span>"}}
81 * //=> 'foo'
82 * ```
83 *
84 * @param {String} `str` The string of HTML to sanitize.
85 * @return {String}
86 * @api public
87 */
88
89 1 helpers.sanitize = function (str) {
90 return sanitize(str);
91 };
92
93
94 /**
95 * Block helper for creating unordered lists (`<ul></ul>`)
96 *
97 * @param {Object} `context`
98 * @return {String}
99 * @block
100 * @api public
101 */
102
103 1 helpers.ul = function (context) {
104 const options = arguments[arguments.length - 1];
105 1 return ('<ul ' + (parseAttributes(options.hash)) + '>') + context.map(function (item) {
106
if (
typeof item !== 'string'
) {
107 2 item = options.fn(item);
108 }
109 2 return '<li>' + item + '</li>';
110 }).join('\n') + '</ul>';
111 };
112
113 /**
114 * Block helper for creating ordered lists (`<ol></ol>`)
115 *
116 * @param {Object} `context`
117 * @return {String}
118 * @block
119 * @api public
120 */
121
122 1 helpers.ol = function (context) {
123 const options = arguments[arguments.length - 1];
124 1 return ('<ol ' + (parseAttributes(options.hash)) + '>') + context.map(function (item) {
125
if (
typeof item !== 'string'
) {
126 2 item = options.fn(item);
127 }
128 2 return '<li>' + item + '</li>';
129 }).join('\n') + '</ol>';
130 };
131
132 /**
133 * Returns a `<figure>` with a thumbnail linked to a full picture
134 *
135 * @param {Object} `context` Object with values/attributes to add to the generated elements:
136 * @param {String} `context.alt`
137 * @param {String} `context.src`
138 * @param {Number} `context.width`
139 * @param {Number} `context.height`
140 * @return {String} HTML `<figure>` element with image and optional caption/link.
141 * @contributor: Marie Hogebrandt <https://github.com/Melindrea>
142 * @api public
143 */
144
145 1 helpers.thumbnailImage = function (context) {
146 var figure = '';
147 7 var image = '';
148
149 7 var link = context.full || false;
150 7 var imageAttributes = {
151 alt: context.alt,
152 src: context.thumbnail,
153 width: context.size.width,
154 height: context.size.height
155 };
156
157 7 var figureAttributes = { id: 'image-' + context.id };
158 7 var linkAttributes = { href: link, rel: 'thumbnail' };
159
160 7 if (context.classes) {
161 3 if (context.classes.image) {
162 1 imageAttributes.class = context.classes.image.join(' ');
163 }
164 3 if (context.classes.figure) {
165 1 figureAttributes.class = context.classes.figure.join(' ');
166 }
167 3 if (context.classes.link) {
168 1 linkAttributes.class = context.classes.link.join(' ');
169 }
170 }
171
172 7 figure += '<figure ' + parseAttributes(figureAttributes) + '>\n';
173 7 image += '<img ' + parseAttributes(imageAttributes) + '>\n';
174
175 7 if (link) {
176 5 figure += '<a ' + parseAttributes(linkAttributes) + '>\n' + image + '</a>\n';
177 } else {
178 2 figure += image;
179 }
180
181 7 if (context.caption) {
182 5 figure += '<figcaption>' + context.caption + '</figcaption>\n';
183 }
184
185 7 figure += '</figure>';
186 7 return figure;
187 };

helpers/3p/inflection.js

100%
31
31
0
Line Lint Hits Source
1 'use strict';
2
3 1 var utils = require('./utils');
4
5 /**
6 * Expose `helpers`
7 */
8
9 1 var helpers = module.exports;
10
11 /**
12 * @name .inflect
13 * @param {Number} `count`
14 * @param {String} `singular` The singular form
15 * @param {String} `plural` The plural form
16 * @param {String} `include`
17 * @return {String}
18 * @api public
19 */
20
21 1 helpers.inflect = function(...args) {
22 args.pop();
23 2 const [count, singular, plural, include] = args;
24 2 var word = (count > 1 || count === 0) ? plural : singular;
25
26 2 if (utils.isUndefined(include) || include === false) {
27 1 return word;
28 } else {
29 1 return String(count) + ' ' + word;
30 }
31 };
32
33 /**
34 * Returns an ordinalized number (as a string).
35 *
36 * ```handlebars
37 * {{ordinalize 1}}
38 * //=> '1st'
39 * {{ordinalize 21}}
40 * //=> '21st'
41 * {{ordinalize 29}}
42 * //=> '29th'
43 * {{ordinalize 22}}
44 * //=> '22nd'
45 * ```
46 *
47 * @param {String} `val` The value to ordinalize.
48 * @return {String} The ordinalized number
49 * @api public
50 */
51
52 1 helpers.ordinalize = function(val) {
53 var num = Math.abs(Math.round(val));
54 7 var res = num % 100;
55
56 7 if (utils.indexOf([11, 12, 13], res) >= 0) {
57 1 return '' + val + 'th';
58 }
59
60 6 switch (num % 10) {
61 case 1:
62 2 return '' + val + 'st';
63 case 2:
64 2 return '' + val + 'nd';
65 case 3:
66 1 return '' + val + 'rd';
67 default: {
68 1 return '' + val + 'th';
69 }
70 }
71 };

helpers/3p/markdown.js

100%
6
6
0
Line Lint Hits Source
1 'use strict';
2
3 1 const markdown = require('./utils/lib/markdown');
4 /**
5 * Expose markdown `helpers` (for performance we're using getters so
6 * that the helpers are only loaded if called)
7 */
8
9 1 var helpers = module.exports;
10
11 /**
12 * Block helper that converts a string of inline markdown to HTML.
13 *
14 * ```html
15 * {{#markdown}}
16 * # Foo
17 * {{/markdown}}
18 * //=> <h1>Foo</h1>
19 * ```
20 * @name .markdown
21 * @param {Object} `context`
22 * @param {Object} `options`
23 * @return {String}
24 * @block
25 * @api public
26 */
27
28 1 helpers.markdown = function(context, options) {
29 return markdown()(context, options);
30 }
31

helpers/3p/math.js

100%
40
40
0
Line Lint Hits Source
1 'use strict';
2
3 1 var utils = require('./utils');
4
5 /**
6 * Expose `helpers`
7 */
8
9 1 var helpers = module.exports;
10
11 /**
12 * Return the product of `a` plus `b`.
13 *
14 * @param {Number} `a`
15 * @param {Number} `b`
16 * @api public
17 */
18
19 1 helpers.add = function(a, b) {
20 return a + b;
21 };
22
23 /**
24 * Return the product of `a` minus `b`.
25 *
26 * @param {Number} `a`
27 * @param {Number} `b`
28 * @api public
29 */
30
31 1 helpers.subtract = function(a, b) {
32 return a - b;
33 };
34
35 /**
36 * Divide `a` by `b`
37 *
38 * @param {Number} `a` numerator
39 * @param {Number} `b` denominator
40 * @api public
41 */
42
43 1 helpers.divide = function(a, b) {
44 return a / b;
45 };
46
47 /**
48 * Multiply `a` by `b`.
49 *
50 * @param {Number} `a` factor
51 * @param {Number} `b` multiplier
52 * @api public
53 */
54
55 1 helpers.multiply = function(a, b) {
56 return a * b;
57 };
58
59 /**
60 * Get the `Math.floor()` of the given value.
61 *
62 * @param {Number} `value`
63 * @api public
64 */
65
66 1 helpers.floor = function(value) {
67 return Math.floor(value);
68 };
69
70 /**
71 * Get the `Math.ceil()` of the given value.
72 *
73 * @param {Number} `value`
74 * @api public
75 */
76
77 1 helpers.ceil = function(value) {
78 return Math.ceil(value);
79 };
80
81 /**
82 * Round the given value.
83 *
84 * @param {Number} `value`
85 * @api public
86 */
87
88 1 helpers.round = function(value) {
89 return Math.round(value);
90 };
91
92 /**
93 * Returns the sum of all numbers in the given array.
94 *
95 * ```handlebars
96 * {{sum "[1, 2, 3, 4, 5]"}}
97 * //=> '15'
98 * ```
99 *
100 * @name .sum
101 * @param {Array} `array` Array of numbers to add up.
102 * @return {Number}
103 * @api public
104 */
105
106 1 helpers.sum = function() {
107 var args = utils.flatten([].concat.apply([], arguments));
108 7 var i = args.length, sum = 0;
109 7 while (i--) {
110 30 if (!utils.isNumber(args[i])) {
111 4 continue;
112 }
113 26 sum += (+args[i]);
114 }
115 7 return sum;
116 };
117
118 /**
119 * Returns the average of all numbers in the given array.
120 *
121 * ```handlebars
122 * {{avg "[1, 2, 3, 4, 5]"}}
123 * //=> '3'
124 * ```
125 *
126 * @name .avg
127 * @param {Array} `array` Array of numbers to add up.
128 * @return {Number}
129 * @api public
130 */
131
132 1 helpers.avg = function() {
133 var args = utils.flatten([].concat.apply([], arguments));
134 // remove handlebars options object
135 3 args.pop();
136 3 return exports.sum(args) / args.length;
137 };

helpers/3p/misc.js

95.45%
22
21
1
Line Lint Hits Source
1 'use strict';
2
3 /**
4 * Expose `helpers`
5 */
6
7 1 var helpers = module.exports;
8
9 /**
10 * Returns the first value if defined, otherwise the "default" value is returned.
11 *
12 * @param {any} `value`
13 * @param {any} `defaultValue`
14 * @return {String}
15 * @alias .or
16 * @api public
17 */
18
19 1 helpers.default = function(...args) {
20 args.pop();
21 3 const value = args.shift();
22 3 const defaultValue = args.shift();
23 3 return !value
24 ? defaultValue
25 : value;
26 };
27
28
29 /**
30 * Block helper that renders the block without taking any arguments.
31 *
32 * @return {String}
33 * @block
34 * @api public
35 */
36
37 1 helpers.noop = function() {
38 const options = arguments[arguments.length - 1];
39 1 return options.fn(this);
40 };
41
42 /**
43 * Block helper that builds the context for the block
44 * from the options hash.
45 *
46 * @contributor Vladimir Kuznetsov <https://github.com/mistakster>
47 * @block
48 * @api public
49 */
50
51 1 helpers.withHash = function() {
52 const options = arguments[arguments.length - 1];
53
if (
options.hash
&& Object.keys(options.hash).length) {
54 3 return options.fn(options.hash);
55 } else {
56 3 return options.inverse(this);
57 }
58 };

helpers/3p/number.js

87.67%
73
64
9
Line Lint Hits Source
1 'use strict';
2
3 1 var utils = require('./utils');
4
5 /**
6 * Expose `helpers`
7 */
8
9 1 var helpers = module.exports;
10
11 /**
12 * Add commas to numbers
13 *
14 * @param {Number} `num`
15 * @return {Number}
16 * @api public
17 */
18
19 1 helpers.addCommas = function(num) {
20 return num.toString().replace(/(\d)(?=(\d\d\d)+(?!\d))/g, '$1,');
21 };
22
23 /**
24 * Convert a string or number to a formatted phone number.
25 *
26 * @param {Number|String} `num` The phone number to format, e.g. `8005551212`
27 * @return {Number} Formatted phone number: `(800) 555-1212`
28 * @source http://bit.ly/QlPmPr
29 * @api public
30 */
31
32 1 helpers.phoneNumber = function(num) {
33 num = num.toString();
34
35 1 return '(' + num.substr(0, 3) + ') '
36 + num.substr(3, 3) + '-'
37 + num.substr(6, 4);
38 };
39
40 /**
41 * Generate a random number between two values
42 *
43 * @param {Number} `min`
44 * @param {Number} `max`
45 * @contributor Tim Douglas <https://github.com/timdouglas>
46 * @return {String}
47 * @api public
48 */
49
50 1 helpers.random = function(min, max) {
51 return Math.floor(Math.random() * (max - min + 1)) + min;
52 };
53
54 /**
55 * Abbreviate numbers to the given number of `precision`. This is for
56 * general numbers, not size in bytes.
57 *
58 * @param {Number} `number`
59 * @param {Number} `precision`
60 * @return {String}
61 * @api public
62 */
63
64 1 helpers.toAbbr = function(number, precision) {
65
if (
!utils.isNumber(number)
) {
66 number = 0;
67 }
68 8 if (utils.isUndefined(precision)) {
69 6 precision = 2;
70 }
71
72 8 number = +number;
73 // 2 decimal places => 100, 3 => 1000, etc.
74 8 precision = Math.pow(10, precision);
75 8 var abbr = ['k', 'm', 'b', 't', 'q'];
76 8 var len = abbr.length - 1;
77
78
while (
len >= 0
) {
79 26 var size = Math.pow(10, (len + 1) * 3);
80 26 if (size <= (number + 1)) {
81 8 number = Math.round(number * precision / size) / precision;
82 8 number += abbr[len];
83 8 break;
84 }
85 18 len--;
86 }
87 8 return number;
88 };
89
90 /**
91 * Returns a string representing the given number in exponential notation.
92 *
93 * ```js
94 * {{toExponential number digits}};
95 * ```
96 * @param {Number} `number`
97 * @param {Number} `fractionDigits` Optional. An integer specifying the number of digits to use after the decimal point. Defaults to as many digits as necessary to specify the number.
98 * @return {Number}
99 * @api public
100 */
101
102 1 helpers.toExponential = function(number, digits) {
103
if (
!utils.isNumber(number)
) {
104 number = 0;
105 }
106 2 if (utils.isUndefined(digits)) {
107 1 digits = 0;
108 }
109 2 number = +number;
110 2 return number.toExponential(digits);
111 };
112
113 /**
114 * Formats the given number using fixed-point notation.
115 *
116 * @param {Number} `number`
117 * @param {Number} `digits` Optional. The number of digits to use after the decimal point; this may be a value between 0 and 20, inclusive, and implementations may optionally support a larger range of values. If this argument is omitted, it is treated as 0.
118 * @return {Number}
119 * @api public
120 */
121
122 1 helpers.toFixed = function(number, digits) {
123
if (
!utils.isNumber(number)
) {
124 number = 0;
125 }
126 2 if (utils.isUndefined(digits)) {
127 1 digits = 0;
128 }
129 2 number = +number;
130 2 return number.toFixed(digits);
131 };
132
133 /**
134 * @param {Number} `number`
135 * @return {Number}
136 * @api public
137 */
138
139 1 helpers.toFloat = function(number) {
140 return parseFloat(number);
141 };
142
143 /**
144 * @param {Number} `number`
145 * @return {Number}
146 * @api public
147 */
148
149 1 helpers.toInt = function(number) {
150 return parseInt(number, 10);
151 };
152
153 /**
154 * @param {Number} `number`
155 * @param {Number} `precision` Optional. The number of significant digits.
156 * @return {Number}
157 * @api public
158 */
159
160 1 helpers.toPrecision = function(number, precision) {
161
if (
!utils.isNumber(number)
) {
162 number = 0;
163 }
164 2 if (utils.isUndefined(precision)) {
165 1 precision = 1;
166 }
167 2 number = +number;
168 2 return number.toPrecision(precision);
169 };

helpers/3p/object.js

95.70%
93
89
4
Line Lint Hits Source
1 'use strict';
2
3 1 var hasOwn = Object.hasOwnProperty;
4 1 var utils = require('./utils/');
5
6 /**
7 * Expose `helpers`
8 */
9
10 1 var helpers = module.exports;
11
12 /**
13 * Extend the context with the properties of other objects.
14 * A shallow merge is performed to avoid mutating the context.
15 *
16 * @param {Object} `objects` One or more objects to extend.
17 * @return {Object}
18 * @api public
19 */
20
21 1 helpers.extend = function(/*objects*/) {
22 var args = [].slice.call(arguments);
23 3 var last = args[args.length - 1];
24
25
if (
last
&&
utils.isObject(last)
&& last.hash) {
26 1 last = last.hash;
27 1 args.pop(); // remove handlebars options object
28 1 args.push(last);
29 }
30
31 3 var len = args.length;
32 3 var context = {};
33 3 var i = -1;
34
35 3 while (++i < len) {
36 10 var obj = args[i];
37 10 if (utils.isObject(obj)) {
38 9 for (var key in obj) {
39
if (
Object.hasOwnProperty.call(obj, key)
) {
40 8 context[key] = obj[key];
41 }
42 }
43 }
44 }
45 3 return context;
46 };
47
48 /**
49 * Block helper that iterates over the properties of
50 * an object, exposing each key and value on the context.
51 *
52 * @name .forIn
53 * @param {Object} `context`
54 * @return {String}
55 * @block
56 * @api public
57 */
58
59 1 helpers.forIn = function(obj) {
60 const options = arguments[arguments.length - 1];
61 3 if (utils.isOptions(obj)) {
62 1 return obj.inverse(this);
63 }
64
65 2 var data = utils.createFrame(options, options.hash);
66 2 var result = '';
67
68 2 for (var key in obj) {
69 4 data.key = key;
70 4 result += options.fn(obj[key], {data: data});
71 }
72 2 return result;
73 };
74
75 /**
76 * Block helper that iterates over the **own** properties of
77 * an object, exposing each key and value on the context.
78 *
79 * @name .forOwn
80 * @param {Object} `obj` The object to iterate over.
81 * @return {String}
82 * @block
83 * @api public
84 */
85
86 1 helpers.forOwn = function(obj) {
87 const options = arguments[arguments.length - 1];
88 6 if (utils.isOptions(obj)) {
89 1 return obj.inverse(this);
90 }
91
92 5 var data = utils.createFrame(options, options.hash);
93 5 var result = '';
94
95 5 for (var key in obj) {
96 13 if (Object.hasOwnProperty.call(obj, key)) {
97 12 data.key = key;
98 12 result += options.fn(obj[key], {data: data});
99 }
100 }
101 5 return result;
102 };
103
104 /**
105 * Take arguments and, if they are string or number, convert them to a dot-delineated object property path.
106 *
107 * @name .toPath
108 * @param {String|Number} `prop` The property segments to assemble (can be multiple).
109 * @return {String}
110 * @api public
111 */
112 1 helpers.toPath = function (/*prop*/) {
113 var prop = [];
114 3 for (var i = 0; i < arguments.length; i++) {
115 12 if (typeof arguments[i] === "string" || typeof arguments[i] === "number") {
116 9 prop.push(arguments[i]);
117 }
118 }
119 3 return prop.join('.');
120 };
121
122
123 /**
124 * Return true if `key` is an own, enumerable property
125 * of the given `context` object.
126 *
127 * ```handlebars
128 * {{hasOwn context key}}
129 * ```
130 *
131 * @name .hasOwn
132 * @param {String} `key`
133 * @param {Object} `context` The context object.
134 * @return {Boolean}
135 * @api public
136 */
137
138 1 helpers.hasOwn = function(context, key) {
139 return hasOwn.call(context, key);
140 };
141
142 /**
143 * Return true if `value` is an object.
144 *
145 * ```handlebars
146 * {{isObject "foo"}}
147 * //=> false
148 * ```
149 * @name .isObject
150 * @param {String} `value`
151 * @return {Boolean}
152 * @api public
153 */
154
155 1 helpers.isObject = function(value) {
156
return
value
&& typeof value === 'object'
157 && !Array.isArray(value);
158 };
159
160 /**
161 * Deeply merge the properties of the given `objects` with the
162 * context object.
163 *
164 * @name .merge
165 * @param {Object} `object` The target object. Pass an empty object to shallow clone.
166 * @param {Object} `objects`
167 * @return {Object}
168 * @api public
169 */
170
171 1 helpers.merge = function(context/*, objects, options*/) {
172 var args = [].slice.call(arguments);
173 1 var last = args[args.length - 1];
174
175
if (
last
&&
typeof last === 'object'
&& last.hash) {
176 1 last = last.hash;
177 1 args.pop(); // remove handlebars options object
178 1 args.push(last);
179 }
180
181 1 context = utils.merge.apply(utils.merge, args);
182 1 return context;
183 };
184
185 /**
186 * Block helper that parses a string using `JSON.parse`,
187 * then passes the parsed object to the block as context.
188 *
189 * @param {String} `string` The string to parse
190 * @contributor github.com/keeganstreet
191 * @block
192 * @api public
193 */
194
195 1 helpers.JSONparse = function(str) {
196 const options = arguments[arguments.length - 1];
197 4 return options.fn(JSON.parse(str));
198 };
199
200 /**
201 * Stringify an object using `JSON.stringify`.
202 *
203 * @param {Object} `obj` Object to stringify
204 * @return {String}
205 * @api public
206 */
207
208 1 helpers.JSONstringify = function(obj, indent) {
209 if (!utils.isNumber(indent)) {
210 4 indent = 0;
211 }
212 5 return JSON.stringify(obj, null, indent);
213 };
214
215 /**
216 * Alias for JSONstringify. this will be
217 * deprecated in a future release
218 */
219
220 1 helpers.stringify = helpers.JSONstringify;

helpers/3p/string.js

91.28%
149
136
13
Line Lint Hits Source
1 'use strict';
2
3 1 var utils = require('./utils');
4
5 /**
6 * Expose `helpers`
7 */
8
9 1 var helpers = module.exports;
10
11 /**
12 * camelCase the characters in the given `string`.
13 *
14 * ```js
15 * {{camelcase "foo bar baz"}};
16 * //=> 'fooBarBaz'
17 * ```
18 *
19 * @name .camelcase
20 * @param {String} `string` The string to camelcase.
21 * @return {String}
22 * @api public
23 */
24
25 1 helpers.camelcase = function(str) {
26 return utils.changecase(str, function(ch) {
27 return ch.toUpperCase();
28 });
29 };
30
31 /**
32 * Capitalize the first word in a sentence.
33 *
34 * ```handlebars
35 * {{capitalize "foo bar baz"}}
36 * //=> "Foo bar baz"
37 * ```
38 * @param {String} `str`
39 * @return {String}
40 * @api public
41 */
42
43 1 helpers.capitalize = function(str) {
44
if (
str
&& typeof str === 'string') {
45 18 return str.charAt(0).toUpperCase()
46 + str.slice(1);
47 }
48 };
49
50 /**
51 * Capitalize all words in a string.
52 *
53 * ```handlebars
54 * {{capitalizeAll "foo bar baz"}}
55 * //=> "Foo Bar Baz"
56 * ```
57 * @param {String} `str`
58 * @return {String}
59 * @api public
60 */
61
62 1 helpers.capitalizeAll = function(str) {
63
if (
str
&& typeof str === 'string') {
64 1 return str.replace(/\w\S*/g, function(word) {
65 return exports.capitalize(word);
66 });
67 }
68 };
69
70 /**
71 * Center a string using non-breaking spaces
72 *
73 * @param {String} `str`
74 * @param {String} `spaces`
75 * @return {String}
76 * @api public
77 */
78
79 1 helpers.center = function(str, spaces) {
80
if (
str
&& typeof str === 'string') {
81 1 var space = '';
82 1 var i = 0;
83 1 while (i < spaces) {
84 2 space += '&nbsp;';
85 2 i++;
86 }
87 1 return space + str + space;
88 }
89 };
90
91 /**
92 * Like trim, but removes both extraneous whitespace **and
93 * non-word characters** from the beginning and end of a string.
94 *
95 * ```js
96 * {{chop "_ABC_"}}
97 * //=> 'ABC'
98 *
99 * {{chop "-ABC-"}}
100 * //=> 'ABC'
101 *
102 * {{chop " ABC "}}
103 * //=> 'ABC'
104 * ```
105 *
106 * @name .chop
107 * @param {String} `string` The string to chop.
108 * @return {String}
109 * @api public
110 */
111
112 1 helpers.chop = function(str) {
113 return utils.chop(str);
114 };
115
116 /**
117 * dash-case the characters in `string`. Replaces non-word
118 * characters and periods with hyphens.
119 *
120 * ```js
121 * {{dashcase "a-b-c d_e"}}
122 * //=> 'a-b-c-d-e'
123 * ```
124 *
125 * @param {String} `string`
126 * @return {String}
127 * @api public
128 */
129
130 1 helpers.dashcase = function(str) {
131 return utils.changecase(str, function(ch) {
132 return '-' + ch;
133 });
134 };
135
136 /**
137 * dot.case the characters in `string`.
138 *
139 * ```js
140 * {{dotcase "a-b-c d_e"}}
141 * //=> 'a.b.c.d.e'
142 * ```
143 *
144 * @param {String} `string`
145 * @return {String}
146 * @api public
147 */
148
149 1 helpers.dotcase = function(str) {
150 return utils.changecase(str, function(ch) {
151 return '.' + ch;
152 });
153 };
154
155 /**
156 * Replace spaces in a string with hyphens.
157 *
158 * ```handlebars
159 * {{hyphenate "foo bar baz qux"}}
160 * //=> "foo-bar-baz-qux"
161 * ```
162 * @param {String} `str`
163 * @return {String}
164 * @api public
165 */
166
167 1 helpers.hyphenate = function(str) {
168
if (
str
&& typeof str === 'string') {
169 2 return str.split(' ').join('-');
170 }
171 };
172
173 /**
174 * Return true if `value` is a string.
175 *
176 * ```handlebars
177 * {{isString "foo"}}
178 * //=> 'true'
179 * ```
180 * @param {String} `value`
181 * @return {Boolean}
182 * @api public
183 */
184
185 1 helpers.isString = function(value) {
186 return utils.isString(value);
187 };
188
189 /**
190 * Lowercase all characters in the given string.
191 *
192 * ```handlebars
193 * {{lowercase "Foo BAR baZ"}}
194 * //=> 'foo bar baz'
195 * ```
196 * @param {String} `str`
197 * @return {String}
198 * @api public
199 */
200
201 1 helpers.lowercase = function(str) {
202
if (
str
&& typeof str === 'string') {
203 1 return str.toLowerCase();
204 }
205 };
206
207 /**
208 * PascalCase the characters in `string`.
209 *
210 * ```js
211 * {{pascalcase "foo bar baz"}}
212 * //=> 'FooBarBaz'
213 * ```
214 *
215 * @name .pascalcase
216 * @param {String} `string`
217 * @return {String}
218 * @api public
219 */
220
221 1 helpers.pascalcase = function(str) {
222 str = utils.changecase(str, function(ch) {
223 return ch.toUpperCase();
224 });
225 4 return str.charAt(0).toUpperCase()
226 + str.slice(1);
227 };
228
229 /**
230 * path/case the characters in `string`.
231 *
232 * ```js
233 * {{pathcase "a-b-c d_e"}}
234 * //=> 'a/b/c/d/e'
235 * ```
236 *
237 * @param {String} `string`
238 * @return {String}
239 * @api public
240 */
241
242 1 helpers.pathcase = function(str) {
243 return utils.changecase(str, function(ch) {
244 return '/' + ch;
245 });
246 };
247
248 /**
249 * Replace spaces in the given string with pluses.
250 *
251 * ```handlebars
252 * {{plusify "foo bar baz"}}
253 * //=> 'foo+bar+baz'
254 * ```
255 * @param {String} `str` The input string
256 * @return {String} Input string with spaces replaced by plus signs
257 * @source Stephen Way <https://github.com/stephenway>
258 * @api public
259 */
260
261 1 helpers.plusify = function(str) {
262 if (str && typeof str === 'string') {
263 2 return str.split(' ').join('+');
264 }
265 };
266
267 /**
268 * Reverse a string.
269 *
270 * ```handlebars
271 * {{reverse "abcde"}}
272 * //=> 'edcba'
273 * ```
274 * @name .reverse
275 * @param {String} `str`
276 * @return {String}
277 * @api public
278 */
279
280 1 helpers.reverse = function(str) {
281
if (
str
&& typeof str === 'string') {
282 1 return str.split('').reverse().join('');
283 }
284 };
285
286 /**
287 * Replace all occurrences of `a` with `b`.
288 *
289 * ```handlebars
290 * {{replace "a b a b a b" "a" "z"}}
291 * //=> 'z b z b z b'
292 * ```
293 * @param {String} `str`
294 * @param {String} `a`
295 * @param {String} `b`
296 * @return {String}
297 * @api public
298 */
299
300 1 helpers.replace = function(str, a, b) {
301
if (
str
&& typeof str === 'string') {
302
if (
!a
|| typeof a !== 'string') {return str;}
303
if (
!b
|| typeof b !== 'string') {b = '';}
304 2 return str.split(a).join(b);
305 }
306 };
307
308 /**
309 * Sentence case the given string
310 *
311 * ```handlebars
312 * {{sentence "hello world. goodbye world."}}
313 * //=> 'Hello world. Goodbye world.'
314 * ```
315 * @param {String} `str`
316 * @return {String}
317 * @api public
318 */
319
320 1 helpers.sentence = function(str) {
321
if (
str
&& typeof str === 'string') {
322 1 var re = /((?:\S[^\.\?\!]*)[\.\?\!]*)/g;
323 1 return str.replace(re, function(txt) {
324 return txt.charAt(0).toUpperCase()
325 + txt.substr(1).toLowerCase();
326 });
327 }
328 };
329
330 /**
331 * snake_case the characters in the given `string`.
332 *
333 * ```js
334 * {{snakecase "a-b-c d_e"}}
335 * //=> 'a_b_c_d_e'
336 * ```
337 *
338 * @param {String} `string`
339 * @return {String}
340 * @api public
341 */
342
343 1 helpers.snakecase = function(str) {
344 return utils.changecase(str, function(ch) {
345 return '_' + ch;
346 });
347 };
348
349 /**
350 * Split `string` by the given `character`.
351 *
352 * ```js
353 * {{split "a,b,c" ","}}
354 * //=> ['a', 'b', 'c']
355 * ```
356 *
357 * @param {String} `string` The string to split.
358 * @return {String} `character` Default is `,`
359 * @api public
360 */
361
362 1 helpers.split = function(str, ch) {
363 1 if (!helpers.isString(str)) {return '';}
364 3 if (typeof ch !== 'string') {ch = ',';}
365 2 return str.split(ch);
366 };
367
368 /**
369 * Tests whether a string begins with the given prefix.
370 *
371 * ```handlebars
372 * {{#startsWith "Goodbye" "Hello, world!"}}
373 * Whoops
374 * {{else}}
375 * Bro, do you even hello world?
376 * {{/startsWith}}
377 * ```
378 * @param {String} `prefix`
379 * @param {String} `testString`
380 * @contributor Dan Fox <http://github.com/iamdanfox>
381 * @return {String}
382 * @block
383 * @api public
384 */
385
386 1 helpers.startsWith = function(prefix, str) {
387 const options = arguments[arguments.length - 1];
388 4 if (str && typeof str === 'string') {
389 2 if (str.indexOf(prefix) === 0) {
390 1 return options.fn(this);
391 }
392 }
393 3 if (typeof options.inverse === 'function') {
394 2 return options.inverse(this);
395 }
396 1 return '';
397 };
398
399 /**
400 * Title case the given string.
401 *
402 * ```handlebars
403 * {{titleize "this is title case"}}
404 * //=> 'This Is Title Case'
405 * ```
406 * @param {String} `str`
407 * @return {String}
408 * @api public
409 */
410
411 1 helpers.titleize = function(str) {
412
if (
str
&& typeof str === 'string') {
413 2 var title = str.replace(/[ \-_]+/g, ' ');
414 2 var words = title.match(/\w+/g);
415 2 if (!words) {
416 1 return str;
417 }
418 1 var len = words.length;
419 1 var res = [];
420 1 var i = 0;
421 1 while (len--) {
422 7 var word = words[i++];
423 7 res.push(exports.capitalize(word));
424 }
425 1 return res.join(' ');
426 }
427 };
428
429 /**
430 * Removes extraneous whitespace from the beginning and end
431 * of a string.
432 *
433 * ```js
434 * {{trim " ABC "}}
435 * //=> 'ABC'
436 * ```
437 *
438 * @name .trim
439 * @param {String} `string` The string to trim.
440 * @return {String}
441 * @api public
442 */
443
444 1 helpers.trim = function(str) {
445 1 if (!helpers.isString(str)) {return '';}
446 2 return str.trim();
447 };
448
449 /**
450 * Uppercase all of the characters in the given string. If used as a
451 * block helper it will uppercase the entire block. This helper
452 * does not support inverse blocks.
453 *
454 * @name .uppercase
455 * @related capitalize capitalizeAll
456 * @param {String} `str` The string to uppercase
457 * @return {String}
458 * @block
459 * @api public
460 */
461
462 1 helpers.uppercase = function(str) {
463 const options = arguments[arguments.length - 1];
464
if (
str
&& typeof str === 'string') {
465 1 return str.toUpperCase();
466 }
467
if (
options
&&
typeof options === 'object'
&& options.fn) {
468 1 return options.fn(this).toUpperCase();
469 }
470 1 return '';
471 };

helpers/3p/url.js

100%
20
20
0
Line Lint Hits Source
1 'use strict';
2
3 1 var url = require('url');
4
5 /**
6 * Expose `helpers`
7 */
8
9 1 var helpers = module.exports;
10
11 /**
12 * Encodes a Uniform Resource Identifier (URI) component
13 * by replacing each instance of certain characters by
14 * one, two, three, or four escape sequences representing
15 * the UTF-8 encoding of the character.
16 *
17 * @param {String} `str` The un-encoded string
18 * @return {String} The endcoded string
19 * @api public
20 */
21
22 1 helpers.encodeURI = function(str) {
23 return encodeURIComponent(str);
24 };
25
26 /**
27 * Decode a Uniform Resource Identifier (URI) component.
28 *
29 * @param {String} `str`
30 * @return {String}
31 * @api public
32 */
33
34 1 helpers.decodeURI = function(str) {
35 return decodeURIComponent(str);
36 };
37
38 /**
39 * Take a base URL, and a href URL, and resolve them as a
40 * browser would for an anchor tag.
41 *
42 * @param {String} `base`
43 * @param {String} `href`
44 * @return {String}
45 * @api public
46 */
47
48 1 helpers.urlResolve = function(base, href) {
49 return url.resolve(base, href);
50 };
51
52 /**
53 * Parses a `url` string into an object.
54 *
55 * @param {String} `str` URL string
56 * @return {String} Returns stringified JSON
57 * @api public
58 */
59
60 1 helpers.urlParse = function(str) {
61 return url.parse(str);
62 };
63
64
65 /**
66 * Strip protocol from a `url`.
67 *
68 * Useful for displaying media that may have an 'http' protocol
69 * on secure connections. Will change 'http://foo.bar to `//foo.bar`
70 *
71 * @name .stripProtocol
72 * @param {String} `str`
73 * @return {String} the url with http protocol stripped
74 * @api public
75 */
76
77 1 helpers.stripProtocol = function(str) {
78 var parsed = url.parse(str);
79 4 delete parsed.protocol;
80 4 return parsed.format();
81 };

helpers/3p/utils/base.js

100%
20
20
0
Line Lint Hits Source
1 'use strict';
2
3 1 const { getValue } = require('../../lib/common');
4 1 const getObject = require('../../../helpers/getObject');
5
6 1 module.exports = {
7 sortBy: require('./lib/arraySort'),
8 filter: require('./lib/arrayFilter'),
9 flatten: require('./lib/arrayFlatten'),
10 iterator: require('./lib/makeIterator'),
11 indexOf: require('./lib/indexOf'),
12 typeOf: require('./lib/kindOf'),
13 isEven: require('./lib/isEven'),
14 isNumber: require('./lib/isNumber'),
15 isOdd: require('./lib/isOdd'),
16 createFrame: require('./lib/createFrame'),
17 get: getValue,
18 getObject: getObject[0].factory(),
19 forOwn: require('./lib/forOwn'),
20 merge: require('./lib/mixinDeep'),
21 deepMatches: require('./lib/deepMatches'),
22 };

helpers/3p/utils/index.js

60.92%
174
106
68
Line Lint Hits Source
1 'use strict';
2
3 1 var utils = require('./base');
4
5 /**
6 * Returns true if the given value is an object.
7 *
8 * ```js
9 * console.log(utils.isObject(null));
10 * //=> false
11 * console.log(utils.isObject([]));
12 * //=> false
13 * console.log(utils.isObject(function() {}));
14 * //=> false
15 * console.log(utils.isObject({}));
16 * //=> true
17 * ```
18 * @param {Object} `val`
19 * @return {Boolean}
20 * @api public
21 */
22 1 utils.isObject = function (val) {
23
return typeof val === 'object';
24 };
25
26 /**
27 * Returns true if the given value contains the given
28 * `object`, optionally passing a starting index.
29 *
30 * @param {Array} val
31 * @param {Object} obj
32 * @param {Number} start
33 * @return {Boolean}
34 */
35
36 1 utils.contains = function (val, obj, start) {
37
var len = val ? val.length : 0;
38 var idx = start < 0
39 ? Math.max(0, len + start)
40 : start;
41
42 var res = false;
43 var i = 0;
44
45 start = idx || 0;
46
47 if (Array.isArray(val)) {
48 res = utils.indexOf(val, obj, start) > -1;
49
50 } else if (utils.isNumber(len)) {
51 res = (typeof val === 'string'
52 ? val.indexOf(obj, start)
53 : utils.indexOf(val, obj, start)) > -1;
54
55 } else {
56 utils.iterator(val, function (ele) {
57
if (start < i++) {
58 return !(res = (ele === obj));
59
}
60 });
61 }
62 return res;
63 };
64
65 /**
66 * Converts a "regex-string" to an actual regular expression.
67 *
68 * ```js
69 * utils.toRegex('"/foo/"');
70 * //=> /foo/
71 * ```
72 * @param {Object} `value`
73 * @return {Boolean}
74 * @api public
75 */
76
77 1 utils.toRegex = function (val) {
78
return new RegExp(val.replace(/^\/|\/$/g, ''));
79 };
80
81 /**
82 * Returns true if the given value appears to be a
83 * regular expression.
84 *
85 * @param {Object} `value`
86 * @return {Boolean}
87 * @api public
88 */
89
90 1 utils.isRegex = function (val) {
91
if (utils.typeOf(val) === 'regexp') {
92 return true;
93
}
94 if (typeof val !== 'string') {
95 return false;
96 }
97 return val.charAt(0) === '/'
98 && val.slice(-1) === '\/';
99 };
100
101 /**
102 * Remove leading and trailing whitespace and non-word
103 * characters from the given string.
104 *
105 * @param {String} `str`
106 * @return {String}
107 */
108
109 1 utils.chop = function (str) {
110 1 if (!utils.isString(str)) { return ''; }
111 8 var re = /^[-_.\W\s]+|[-_.\W\s]+$/g;
112 8 return str.trim().replace(re, '');
113 };
114
115 /**
116 * Change casing on the given `string`, optionally
117 * passing a delimiter to use between words in the
118 * returned string.
119 *
120 * ```js
121 * utils.changecase('fooBarBaz');
122 * //=> 'foo bar baz'
123 *
124 * utils.changecase('fooBarBaz' '-');
125 * //=> 'foo-bar-baz'
126 * ```
127 * @param {String} `string` The string to change.
128 * @return {String}
129 * @api public
130 */
131
132 1 utils.changecase = function (str, fn) {
133 6 if (!utils.isString(str)) { return ''; }
134 18 if (str.length === 1) {
135 12 return str.toLowerCase();
136 }
137
138 6 str = utils.chop(str).toLowerCase();
139
if (
typeof fn !== 'function'
) {
140 fn = utils.identity;
141 }
142
143 6 var re = /[-_.\W\s]+(\w|$)/g;
144 6 return str.replace(re, function (_, ch) {
145 return fn(ch);
146 });
147 };
148
149 /**
150 * Generate a random number
151 *
152 * @param {Number} `min`
153 * @param {Number} `max`
154 * @return {Number}
155 * @api public
156 */
157
158 1 utils.random = function (min, max) {
159
return min + Math.floor(Math.random() * (max - min + 1));
160 };
161
162 /**
163 * Returns true if the given value is `undefined` or
164 * is a handlebars options hash.
165 *
166 * @param {any} `value`
167 * @return {Boolean}
168 * @api public
169 */
170
171 1 utils.isUndefined = function (val) {
172 return typeof val === 'undefined'
173 || (val && !!val.hash);
174 };
175
176 /**
177 * Returns true if the given value appears to be an **options** object.
178 *
179 * @param {Object} `value`
180 * @return {Boolean}
181 * @api public
182 */
183
184 1 utils.isOptions = function (val) {
185 return utils.isObject(val) && Object.hasOwnProperty.call(val, 'hash');
186 };
187
188 /**
189 * Get options from the options hash and `this`.
190 *
191 * @param {Object} `app` The current application instance.
192 * @return {Object}
193 * @api public
194 */
195
196 1 utils.getArgs = function (app, args) {
197
var opts = utils.merge({}, app && app.options);
198 if (!Array.isArray(args)) {
199 args = [].slice.call(args);
200 }
201
202 var last = args[args.length - 1];
203
204 // merge `options.hash` into the options
205 if (utils.isOptions(last)) {
206 var hbsOptions = args.pop();
207 opts = utils.get(opts, hbsOptions.name) || opts;
208 opts = utils.merge({}, opts, hbsOptions.hash);
209
210 // if the last arg is an object, merge it
211 // into the options
212 } else if (utils.isObject(last)) {
213 opts = utils.merge({}, opts, args.pop());
214 }
215
216 args.push(opts);
217 return args;
218 };
219
220 /**
221 * Returns true if the given value is an object
222 * and not an array.
223 *
224 * @param {any} `value`
225 * @return {Boolean}
226 * @api public
227 */
228
229 1 utils.isObject = function (val) {
230 return val && typeof val === 'object'
231 && !Array.isArray(val);
232 };
233
234 /**
235 * Returns true if the given value is empty.
236 *
237 * @param {any} `value`
238 * @return {Boolean}
239 * @api public
240 */
241
242 1 utils.isEmpty = function (val) {
243 if (val === 0 || val === '0') {
244 6 return false;
245 }
246
if (!val || (Array.isArray(val) &&
val.length === 0
)) {
247 27 return true;
248 }
249 111 if (typeof val === 'object' && !Object.keys(val).length) {
250 13 return true;
251 }
252 98 return false;
253 };
254
255 /**
256 * Try to parse the given `string` as JSON. Fails
257 * gracefully if the value cannot be parsed.
258 *
259 * @name .tryParse
260 * @param {String} `string`
261 * @return {Object}
262 * @api public
263 */
264
265 1 utils.tryParse = function (str) {
266 try {
267 7 return JSON.parse(str);
268 } catch (err) { }
269 3 return null;
270 };
271
272 /**
273 * Return the given value. If the value is a function
274 * it will be called, and the result is returned.
275 *
276 * @param {any} `val`
277 * @return {any}
278 * @api public
279 */
280
281 1 utils.result = function (value) {
282
if (
typeof value === 'function'
) {
283 return value();
284 }
285 6 return value;
286 };
287
288 /**
289 * Return the given value, unchanged.
290 *
291 * @param {any} `val`
292 * @return {any}
293 * @api public
294 */
295
296 1 utils.identity = function (val) {
297
return val;
298 };
299
300 /**
301 * Return true if `val` is a string.
302 *
303 * @param {any} `val` The value to check
304 * @return {Boolean}
305 * @api public
306 */
307
308 1 utils.isString = function (val) {
309 return val && typeof val === 'string';
310 };
311
312 /**
313 * Cast `val` to an array.
314 *
315 * @param {any} `val` The value to arrayify.
316 * @return {Array}
317 * @api public
318 */
319
320 1 utils.arrayify = function (val) {
321
return val ? (Array.isArray(val) ? val : [val]) : [];
322 };
323
324 1 utils.isArray = Array.isArray;
325
326 /**
327 * Get the context to use for rendering.
328 *
329 * @param {Object} `thisArg` Optional invocation context `this`
330 * @return {Object}
331 * @api public
332 */
333
334 1 utils.context = function (thisArg, locals, options) {
335
if (
utils.isOptions(thisArg)
) {
336 return utils.context({}, locals, thisArg);
337 }
338 // ensure args are in the correct order
339
if (
utils.isOptions(locals)
) {
340 return utils.context(thisArg, options, locals);
341 }
342
var appContext =
utils.isApp(thisArg)
?
thisArg.context
: {};
343
options =
options
||
{}
;
344
345 // if "options" is not handlebars options, merge it onto locals
346
if (
!utils.isOptions(options)
) {
347 locals = Object.assign({}, locals, options);
348 }
349 // merge handlebars root data onto locals if specified on the hash
350
if (
utils.isOptions(options)
&& options.hash.root === true) {
351 locals = Object.assign({}, options.data.root, locals);
352 }
353 10 var context = Object.assign({}, appContext, locals, options.hash);
354
if (
!utils.isApp(thisArg)
) {
355 10 context = Object.assign({}, thisArg, context);
356 }
357
if (
utils.isApp(thisArg)
&&
thisArg.view
&&
thisArg.view.data
) {
358 context = Object.assign({}, context, thisArg.view.data);
359 }
360 10 return context;
361 };
362
363 /**
364 * Creates an options object from the `context`, `locals` and `options.`
365 * Handlebars' `options.hash` is merged onto the options, and if the context
366 * is created by [templates][], `this.options` will be merged onto the
367 * options as well.
368 *
369 * @param {Object} `context`
370 * @param {Object} `locals` Options or locals
371 * @param {Object} `options`
372 * @return {Boolean}
373 * @api public
374 */
375
376 1 utils.options = function (thisArg, locals, options) {
377
if (
utils.isOptions(thisArg)
) {
378 return utils.options({}, locals, thisArg);
379 }
380
if (
utils.isOptions(locals)
) {
381 return utils.options(thisArg, options, locals);
382 }
383
options =
options
||
{}
;
384
if (
!utils.isOptions(options)
) {
385 locals = Object.assign({}, locals, options);
386 }
387 3 var opts = Object.assign({}, locals, options.hash);
388
if (
utils.isObject(thisArg)
) {
389 3 opts = Object.assign({}, thisArg.options, opts);
390 }
391
if (
opts[options.name]
) {
392 opts = Object.assign({}, opts[options.name], opts);
393 }
394 3 return opts;
395 };
396
397 /**
398 * Returns true if an `app` propery is on the context, which means
399 * the context was created by [assemble][], [templates][], [verb][],
400 * or any other library that follows this convention.
401 *
402 * ```js
403 * Handlebars.registerHelper('example', function(val, options) {
404 * var context = options.hash;
405 * if (utils.isApp(this)) {
406 * context = Object.assign({}, this.context, context);
407 * }
408 * // do stuff
409 * });
410 * ```
411 * @param {any} `value`
412 * @return {Boolean}
413 * @api public
414 */
415
416 1 utils.isApp = function (thisArg) {
417
return
utils.isObject(thisArg)
418
&&
utils.isObject(thisArg.options)
419
&&
utils.isObject(thisArg.app)
;
420 };
421
422 /**
423 * Expose `utils`
424 */
425
426 1 module.exports = utils;

helpers/3p/utils/lib/arrayFilter.js

90.48%
21
19
2
Line Lint Hits Source
1 /*!
2 * COPY of https://raw.githubusercontent.com/jonschlinkert/arr-filter/1.1.1/index.js
3 */
4
5 'use strict';
6
7 1 const iterator = require('./makeIterator');
8 1 const { ValidationError } = require('../../../../lib/errors');
9
10 1 module.exports = function filter(arr, cb, thisArg) {
11
if (
!arr
) {
12 return [];
13 }
14
15 8 if (typeof cb !== 'function') {
16 1 throw new ValidationError('arr-filter expects a callback function.');
17 }
18
19 7 cb = iterator(cb, thisArg);
20 7 var len = arr.length;
21 7 var res = arr.slice();
22 7 var i = 0;
23
24 7 while (len--) {
25 42 if (!cb(arr[len], i++)) {
26 33 res.splice(len, 1);
27 }
28 }
29
30 7 return res;
31 };
32

helpers/3p/utils/lib/arrayFlatten.js

100%
13
13
0
Line Lint Hits Source
1 /*
2 * COPY of https://raw.githubusercontent.com/jonschlinkert/arr-flatten/1.1.0/index.js
3 */
4
5 'use strict';
6
7 1 module.exports = function (arr) {
8 return flat(arr, []);
9 };
10
11 function flat(arr, res) {
12 112 var i = 0, cur;
13 112 var len = arr.length;
14 112 for (; i < len; i++) {
15 239 cur = arr[i];
16 239 Array.isArray(cur) ? flat(cur, res) : res.push(cur);
17 }
18 112 return res;
19 }

helpers/3p/utils/lib/arraySort.js

98.11%
53
52
1
Line Lint Hits Source
1 'use strict';
2
3 /*
4
5 Copy of https://github.com/jonschlinkert/array-sort/blob/0.1.2/index.js
6
7 */
8 1 const kindOf = require('./kindOf');
9 1 const { getValue } = require('../../../lib/common');
10 1 const { ValidationError } = require('../../../../lib/errors');
11
12 /**
13 * Sort an array of objects by one or more properties.
14 *
15 * @param {Array} `arr` The Array to sort.
16 * @param {String|Array|Function} `props` One or more object paths or comparison functions.
17 * @param {Object} `opts` Pass `{ reverse: true }` to reverse the sort order.
18 * @return {Array} Returns a sorted array.
19 * @api public
20 */
21
22 function arraySort(arr, props, opts) {
23 16 if (!arr) {
24 3 return [];
25 }
26
27 13 if (!Array.isArray(arr)) {
28 1 throw new ValidationError('array-sort expects an array.');
29 }
30
31 12 if (arguments.length === 1) {
32 1 return arr.sort();
33 }
34
35 11 var args = flatten([].slice.call(arguments, 1));
36
37 // if the last argument appears to be a plain object,
38 // it's not a valid `compare` arg, so it must be options.
39 11 if (kindOf(args[args.length - 1]) === 'object') {
40 1 opts = args.pop();
41 }
42 11 return arr.sort(sortBy(args, opts));
43 }
44
45 /**
46 * Iterate over each comparison property or function until `1` or `-1`
47 * is returned.
48 *
49 * @param {String|Array|Function} `props` One or more object paths or comparison functions.
50 * @param {Object} `opts` Pass `{ reverse: true }` to reverse the sort order.
51 * @return {Array}
52 */
53
54 function sortBy(props, opts) {
55 11 opts = opts || {};
56
57 11 return function compareFn(a, b) {
58 var len = props.length, i = -1;
59 138 var result;
60
61 138 while (++i < len) {
62 235 result = compare(props[i], a, b);
63 235 if (result !== 0) {
64 134 break;
65 }
66 }
67 138 if (opts.reverse === true) {
68 35 return result * -1;
69 }
70 103 return result;
71 };
72 }
73
74 /**
75 * Compare `a` to `b`. If an object `prop` is passed, then
76 * `a[prop]` is compared to `b[prop]`
77 */
78
79 function compare(prop, a, b) {
80 449 if (typeof prop === 'function') {
81 // expose `compare` to custom function
82 72 return prop(a, b, compare.bind(null, null));
83 }
84 // compare object values
85
if (prop &&
typeof a === 'object'
&& typeof b === 'object') {
86 163 return compare(null, getValue(a, prop), getValue(b, prop));
87 }
88 214 return defaultCompare(a, b);
89 }
90
91 /**
92 * Default compare function used as a fallback
93 * for sorting.
94 */
95
96 function defaultCompare(a, b) {
97 214 return a < b ? -1 : (a > b ? 1 : 0);
98 }
99
100 /**
101 * Flatten the given array.
102 */
103
104 function flatten(arr) {
105 11 return [].concat.apply([], arr);
106 }
107
108 /**
109 * Expose `arraySort`
110 */
111
112 1 module.exports = arraySort;

helpers/3p/utils/lib/createFrame.js

88.37%
43
38
5
Line Lint Hits Source
1 /**
2 *
3 * https://github.com/jonschlinkert/create-frame/blob/master/index.js
4 * https://github.com/jonschlinkert/define-property/blob/master/index.js
5 */
6
7
8 1 const { ValidationError } = require('../../../../lib/errors');
9
10 function extend(obj /* , ...source */) {
11 50 for (var i = 1; i < arguments.length; i++) {
12 50 for (var key in arguments[i]) {
13
if (
Object.prototype.hasOwnProperty.call(arguments[i], key)
) {
14 94 obj[key] = arguments[i][key];
15 }
16 }
17 }
18
19 50 return obj;
20 }
21
22 function defineProperty(obj, prop, val) {
23
if (
typeof obj !== 'object'
&&
typeof obj !== 'function'
) {
24 throw new ValidationError('expected an object or function.');
25 }
26
27
if (
typeof prop !== 'string'
) {
28 throw new ValidationError('expected `prop` to be a string.');
29 }
30
31
32 23 return Object.defineProperty(obj, prop, {
33 configurable: true,
34 enumerable: false,
35 writable: true,
36 value: val
37 });
38 };
39
40 1 module.exports = function createFrame(data) {
41 if (typeof data !== 'object') {
42 1 throw new ValidationError('createFrame expects data to be an object');
43 }
44
45 23 var frame = extend({}, data);
46 23 frame._parent = data;
47
48 23 defineProperty(frame, 'extend', function (data) {
49 extend(this, data);
50 });
51
52 23 if (arguments.length > 1) {
53 18 var args = [].slice.call(arguments, 1);
54 18 var len = args.length, i = -1;
55 18 while (++i < len) {
56 20 frame.extend(args[i] || {});
57 }
58 }
59 23 return frame;
60 };

helpers/3p/utils/lib/deepMatches.js

67.39%
46
31
15
Line Lint Hits Source
1 /**
2 * Recursively compare objects
3 */
4 1 const typeOf = require("./kindOf");
5
6 function deepMatches(val, value) {
7 38 if (typeOf(val) === "object") {
8
if (
Array.isArray(val)
&&
Array.isArray(value)
) {
9 return matchArray(val, value);
10 } else {
11 19 return matchObject(val, value);
12 }
13 } else {
14 19 return isMatch(val, value);
15 }
16 }
17
18 function containsMatch(array, value) {
19 var len = array.length;
20 var i = -1;
21
22 while (++i < len) {
23 if (deepMatches(array[i], value)) {
24 return true;
25 }
26 }
27 return false;
28 }
29
30 function matchArray(arr, value) {
31 var len = value.length;
32 var i = -1;
33
34 while (++i < len) {
35 if (!containsMatch(arr, value[i])) {
36 return false;
37 }
38 }
39 return true;
40 }
41
42 function matchObject(obj, value) {
43 19 for (var key in value) {
44
if (
Object.hasOwnProperty.call(value, key)
) {
45 21 if (deepMatches(obj[key], value[key]) === false) {
46 14 return false;
47 }
48 }
49 }
50 5 return true;
51 }
52
53 function isMatch(target, val) {
54 19 return target === val;
55 }
56
57 1 module.exports = deepMatches;
58

helpers/3p/utils/lib/forIn.js

100%
8
8
0
Line Lint Hits Source
1 /*
2 * COPY of https://github.com/jonschlinkert/for-in/blob/1.0.1/index.js
3 */
4
5 'use strict';
6
7 1 module.exports = function forIn(obj, fn, thisArg) {
8 for (var key in obj) {
9 211 if (fn.call(thisArg, obj[key], key, obj) === false) {
10 1 break;
11 }
12 }
13 };

helpers/3p/utils/lib/forOwn.js

90%
10
9
1
Line Lint Hits Source
1
2 /*
3 * COPY of https://github.com/jonschlinkert/for-own/tree/0.1.5/index.js
4 */
5
6 'use strict';
7
8 1 var forIn = require('./forIn');
9 1 var hasOwn = Object.prototype.hasOwnProperty;
10
11 1 module.exports = function forOwn(obj, fn, thisArg) {
12 forIn(obj, function(val, key) {
13
if (
hasOwn.call(obj, key)
) {
14 3 return fn.call(thisArg, obj[key], key, obj);
15 }
16 });
17 };

helpers/3p/utils/lib/indexOf.js

90%
20
18
2
Line Lint Hits Source
1 /*
2 * COPY of https://github.com/jonschlinkert/index-of/blob/0.2.0/index.js
3 */
4
5
6 'use strict';
7
8 1 module.exports = function indexOf(arr, ele, start) {
9 start = start || 0;
10 19 var idx = -1;
11
12 20 if (!arr) {return idx;}
13 18 var len = arr.length;
14
var i =
start < 0
15
? (
len + start
)
16 : start;
17
18 18 if (i >= arr.length) {
19 1 return -1;
20 }
21
22 17 while (i < len) {
23 49 if (arr[i] === ele) {
24 8 return i;
25 }
26 41 i++;
27 }
28
29 9 return -1;
30 };

helpers/3p/utils/lib/isEven.js

100%
5
5
0
Line Lint Hits Source
1 'use strict';
2
3 /**
4 * Copy of https://raw.githubusercontent.com/i-voted-for-trump/is-even/0.1.2/index.js
5 */
6
7 1 var isOdd = require('./isOdd');
8
9 1 module.exports = function isEven(i) {
10 return !isOdd(i);
11 };

helpers/3p/utils/lib/isExtendable.js

100%
4
4
0
Line Lint Hits Source
1 1 const isPlainObject = require('./isPlainObject');
2
3 1 module.exports = function isExtendable(val) {
4 return isPlainObject(val) || typeof val === 'function' || Array.isArray(val);
5 };

helpers/3p/utils/lib/isNumber.js

90%
10
9
1
Line Lint Hits Source
1 'use strict';
2
3 1 module.exports = function(num) {
4 if (typeof num === 'number') {
5 155 return num - num === 0;
6 }
7 71 if (typeof num === 'string' && num.trim() !== '') {
8
return
Number.isFinite
? Number.isFinite(+num) :
isFinite(+num)
;
9 }
10 33 return false;
11 };

helpers/3p/utils/lib/isOdd.js

100%
12
12
0
Line Lint Hits Source
1 /*
2 Copy form https://github.com/i-voted-for-trump/is-odd/blob/0.1.2/index.js
3 */
4 'use strict';
5
6 1 const { ValidationError } = require('../../../../lib/errors');
7 1 const isNumber = require('./isNumber');
8
9 1 module.exports = function isOdd(i) {
10 if (!isNumber(i)) {
11 3 throw new ValidationError('is-odd expects a number.');
12 }
13 28 if (Number(i) !== Math.floor(i)) {
14 2 throw new ValidationError('is-odd expects an integer.');
15 }
16 26 return !!(~~i & 1);
17 };

helpers/3p/utils/lib/isPlainObject.js

93.75%
16
15
1
Line Lint Hits Source
1 function isObject(o) {
2 608 return Object.prototype.toString.call(o) === '[object Object]';
3 }
4
5 function isPlainObject(o) {
6 450 var ctor,prot;
7
8 741 if (isObject(o) === false) {return false;}
9
10 // If has modified constructor
11 159 ctor = o.constructor;
12 160 if (ctor === undefined) {return true;}
13
14 // If has modified prototype
15 158 prot = ctor.prototype;
16
if (
isObject(prot) === false
) {return false;}
17
18 // If constructor does not have an Object-specific method
19 158 if (Object.hasOwnProperty.call(prot, 'isPrototypeOf') === false) {
20 1 return false;
21 }
22
23 // Most likely a plain Object
24 157 return true;
25 };
26
27 1 module.exports = isPlainObject;
28

helpers/3p/utils/lib/kindOf.js

89.83%
59
53
6
Line Lint Hits Source
1 1 var toString = Object.prototype.toString;
2
3 /**
4 *
5 * COPY of https://raw.githubusercontent.com/jonschlinkert/kind-of/2.0.0/index.js
6 *
7 *
8 * Get the native `typeof` a value.
9 *
10 * @param {*} `val`
11 * @return {*} Native javascript type
12 */
13
14 1 module.exports = function kindOf(val) {
15 // primitivies
16 if (typeof val === 'undefined') {
17 19 return 'undefined';
18 }
19 89 if (val === null) {
20 2 return 'null';
21 }
22 87 if (val === true || val === false || val instanceof Boolean) {
23 3 return 'boolean';
24 }
25 84 if (typeof val === 'string' || val instanceof String) {
26 9 return 'string'
27 }
28 75 if (typeof val === 'number' || val instanceof Number) {
29 12 return 'number'
30 }
31
32 // functions
33 63 if (typeof val === 'function' || val instanceof Function) {
34 23 return 'function'
35 }
36
37 // array
38
if (
typeof Array.isArray !== 'undefined'
&& Array.isArray(val)) {
39 3 return 'array';
40 }
41
42 // check for instances of RegExp and Date before calling `toString`
43 37 if (val instanceof RegExp) {
44 4 return 'regexp';
45 }
46 33 if (val instanceof Date) {
47 1 return 'date';
48 }
49
50 // other objects
51 32 var type = toString.call(val);
52
53
if (
type === '[object RegExp]'
) {
54 return 'regexp';
55 }
56
if (
type === '[object Date]'
) {
57 return 'date';
58 }
59 32 if (type === '[object Arguments]') {
60 1 return 'arguments';
61 }
62
63 // buffer
64
if (
typeof Buffer !== 'undefined'
&& Buffer.isBuffer(val)) {
65 1 return 'buffer';
66 }
67
68 // es6: Map, WeakMap, Set, WeakSet
69 30 if (type === '[object Set]') {
70 1 return 'set';
71 }
72 29 if (type === '[object WeakSet]') {
73 1 return 'weakset';
74 }
75 28 if (type === '[object Map]') {
76 1 return 'map';
77 }
78 27 if (type === '[object WeakMap]') {
79 1 return 'weakmap';
80 }
81 26 if (type === '[object Symbol]') {
82 2 return 'symbol';
83 }
84
85 // must be a plain object
86 24 return 'object';
87 };

helpers/3p/utils/lib/makeIterator.js

100%
34
34
0
Line Lint Hits Source
1 /*!
2 * COPY of https://raw.githubusercontent.com/jonschlinkert/make-iterator/0.3.0/index.js
3 */
4 1 const typeOf = require('./kindOf');
5 1 const deepMatches = require('./deepMatches');
6
7 1 module.exports = function makeIterator(target, thisArg) {
8 switch (typeOf(target)) {
9 case 'undefined':
10 case 'null':
11 3 return noop;
12 case 'function':
13 // function is the first to improve perf (most common case)
14 // also avoid using `Function#call` if not needed, which boosts
15 // perf a lot in some cases
16 11 return (typeof thisArg !== 'undefined') ? function(val, i, arr) {
17 return target.call(thisArg, val, i, arr);
18 } : target;
19 case 'object':
20 1 return function(val) {
21 return deepMatches(val, target);
22 };
23 case 'regexp':
24 2 return function(str) {
25 return target.test(str);
26 };
27 case 'string':
28 case 'number':
29 default: {
30 2 return prop(target);
31 }
32 }
33 };
34
35
36 function prop(name) {
37 2 return function(obj) {
38 return obj[name];
39 };
40 }
41
42 function noop(val) {
43 6 return val;
44 }

helpers/3p/utils/lib/markdown.js

83.78%
37
31
6
Line Lint Hits Source
1 1 const merge = require('./mixinDeep');
2 1 const {Remarkable} = require('remarkable');
3 1 const {linkify} = require('remarkable/linkify');
4
5 1 module.exports = function markdown(config) {
6
7 if (typeof config === 'string') {
8 5 return helper.apply(this, arguments);
9 }
10
11 5 config = config || {};
12
if (
config.fn
||
config.hash
|| arguments.length > 1) {
13 return helper.apply(this, arguments);
14 }
15
16 function helper(context, options) {
17
18 12 if (typeof context === 'string') {
19 5 const opts = merge({}, config, options);
20 5 const md = buildRemarkable(opts);
21 5 return md.render(context);
22 }
23
24
if (
typeof context === 'object'
&& typeof context.fn === 'function') {
25 7 options = context;
26 7 context = {};
27 }
28
29 7 options = merge({html: true, breaks: true}, config, options);
30 7 options = merge({}, options, options.markdown, options.hash);
31
if (
Object.hasOwnProperty.call(options, 'lang')
) {
32 options.langPrefix = options.lang;
33 }
34
35 7 const md = buildRemarkable(options);
36
const ctx = merge({}, options, (
this.context
|| this), context);
37 7 return md.render(options.fn(ctx));
38 }
39
40 function buildRemarkable(options) {
41 12 const remarkable = options.linkify === true ? new Remarkable(options).use(linkify) : new Remarkable(options);
42 12 delete options.linkify;
43 12 return remarkable;
44 }
45
46 5 return helper;
47 };
48

helpers/3p/utils/lib/mixinDeep.js

90.32%
31
28
3
Line Lint Hits Source
1 'use strict';
2
3 1 var isExtendable = require('./isExtendable');
4 1 var forIn = require('./forIn');
5
6 function mixinDeep(target, objects) {
7 57 var len = arguments.length, i = 0;
8 57 while (++i < len) {
9 103 var obj = arguments[i];
10 103 if (isObject(obj)) {
11 82 forIn(obj, copy, target);
12 }
13 }
14 57 return target;
15 }
16
17 /**
18 * Copy properties from the source object to the
19 * target object.
20 *
21 * @param {*} `val`
22 * @param {String} `key`
23 */
24
25 function copy(val, key) {
26
if (
!isValidKey(key)
) {
27 return;
28 }
29
30 203 var obj = this[key];
31 203 if (isObject(val) && isObject(obj)) {
32 13 mixinDeep(obj, val);
33 } else {
34 190 this[key] = val;
35 }
36 }
37
38 /**
39 * Returns true if `val` is an object or function.
40 *
41 * @param {any} val
42 * @return {Boolean}
43 */
44
45 function isObject(val) {
46 424 return isExtendable(val) && !Array.isArray(val);
47 }
48
49 /**
50 * Returns true if `key` is a valid key to use when extending objects.
51 *
52 * @param {String} `key`
53 * @return {Boolean}
54 */
55
56 function isValidKey(key) {
57
return
key !== '__proto__'
&&
key !== 'constructor'
&& key !== 'prototype';
58 };
59
60 /**
61 * Expose `mixinDeep`
62 */
63
64 1 module.exports = mixinDeep;

helpers/3p/utils/lib/striptags.js

97.06%
170
165
5
Line Lint Hits Source
1 1 const STATE_OUTPUT = 0,
2 STATE_HTML = 1,
3 STATE_PRE_COMMENT = 2,
4 STATE_COMMENT = 3,
5 WHITESPACE = /\s/,
6 ALLOWED_TAGS_REGEX = /<(\w*)>/g;
7
8 function striptags(html, allowableTags) {
9
var html =
html
||
''
,
10 state = STATE_OUTPUT,
11 depth = 0,
12 output = '',
13 tagBuffer = '',
14 inQuote = false,
15 i, length, c;
16
17 19 if (typeof allowableTags === 'string') {
18 // Parse the string into an array of tags
19 6 allowableTags = parseAllowableTags(allowableTags);
20 13 } else if (!Array.isArray(allowableTags)) {
21 // If it is not an array, explicitly set to null
22 10 allowableTags = null;
23 }
24
25 19 for (i = 0, length = html.length; i < length; i++) {
26 634 c = html[i];
27
28 634 switch (c) {
29 case '<': {
30 // ignore '<' if inside a quote
31 43 if (inQuote) {
32 1 break;
33 }
34
35 // '<' followed by a space is not a valid tag, continue
36 42 if (html[i + 1] == ' ') {
37 1 consumeCharacter(c);
38 1 break;
39 }
40
41 // change to STATE_HTML
42 41 if (state == STATE_OUTPUT) {
43 38 state = STATE_HTML;
44
45 38 consumeCharacter(c);
46 38 break;
47 }
48
49 // ignore additional '<' characters when inside a tag
50 3 if (state == STATE_HTML) {
51 1 depth++;
52 1 break;
53 }
54
55 2 consumeCharacter(c);
56 2 break;
57 }
58
59 case '>': {
60 // something like this is happening: '<<>>'
61 43 if (depth) {
62 1 depth--;
63 1 break;
64 }
65
66 // ignore '>' if inside a quote
67 42 if (inQuote) {
68 1 break;
69 }
70
71 // an HTML tag was closed
72 41 if (state == STATE_HTML) {
73 35 inQuote = state = 0;
74
75 35 if (allowableTags) {
76 26 tagBuffer += '>';
77 26 flushTagBuffer();
78 }
79
80 35 break;
81 }
82
83 // '<!' met its ending '>'
84 6 if (state == STATE_PRE_COMMENT) {
85 1 inQuote = state = 0;
86 1 tagBuffer = '';
87 1 break;
88 }
89
90 // if last two characters were '--', then end comment
91 5 if (state == STATE_COMMENT &&
92 html[i - 1] == '-' &&
93 html[i - 2] == '-') {
94
95 2 inQuote = state = 0;
96 2 tagBuffer = '';
97 2 break;
98 }
99
100 3 consumeCharacter(c);
101 3 break;
102 }
103
104 // catch both single and double quotes
105 case '"':
106 case '\'': {
107
if (
state == STATE_HTML
) {
108 12 if (inQuote == c) {
109 // end quote found
110 5 inQuote = false;
111 7 } else if (!inQuote) {
112 // start quote only if not already in one
113 5 inQuote = c;
114 }
115 }
116
117 12 consumeCharacter(c);
118 12 break;
119 }
120
121 case '!': {
122 5 if (state == STATE_HTML &&
123 html[i - 1] == '<') {
124
125 // looks like we might be starting a comment
126 4 state = STATE_PRE_COMMENT;
127 4 break;
128 }
129
130 1 consumeCharacter(c);
131 1 break;
132 }
133
134 case '-': {
135 // if the previous two characters were '!-', this is a comment
136 8 if (state == STATE_PRE_COMMENT &&
137 html[i - 1] == '-' &&
138 html[i - 2] == '!') {
139
140 2 state = STATE_COMMENT;
141 2 break;
142 }
143
144 6 consumeCharacter(c);
145 6 break;
146 }
147
148 case 'E':
149 case 'e': {
150 // check for DOCTYPE, because it looks like a comment and isn't
151 35 if (state == STATE_PRE_COMMENT &&
152 html.substr(i - 6, 7).toLowerCase() == 'doctype') {
153
154 1 state = STATE_HTML;
155 1 break;
156 }
157
158 34 consumeCharacter(c);
159 34 break;
160 }
161
162 default: {
163 488 consumeCharacter(c);
164 }
165 }
166 }
167
168 function consumeCharacter(c) {
169 585 if (state == STATE_OUTPUT) {
170 249 output += c;
171 336 } else if (allowableTags && state == STATE_HTML) {
172 210 tagBuffer += c;
173 }
174 }
175
176 function flushTagBuffer() {
177 26 var normalized = '',
178 nonWhitespaceSeen = false,
179 i, length, c;
180
181 26 normalizeTagBuffer:
182
for (i = 0, length = tagBuffer.length;
i < length
; i++) {
183 169 c = tagBuffer[i].toLowerCase();
184
185 169 switch (c) {
186 case '<': {
187 26 break;
188 }
189
190 case '>': {
191 22 break normalizeTagBuffer;
192 }
193
194 case '/': {
195 13 nonWhitespaceSeen = true;
196 13 break;
197 }
198
199 default: {
200 108 if (!c.match(WHITESPACE)) {
201 104 nonWhitespaceSeen = true;
202 104 normalized += c;
203
} else if (
nonWhitespaceSeen
) {
204 4 break normalizeTagBuffer;
205 }
206 }
207 }
208 }
209
210 26 if (allowableTags.indexOf(normalized) !== -1) {
211 16 output += tagBuffer;
212 }
213
214 26 tagBuffer = '';
215 }
216
217 19 return output;
218 }
219
220 /**
221 * Return an array containing tags that are allowed to pass through the
222 * algorithm.
223 *
224 * @param string allowableTags A string of tags to allow (e.g. "<b><strong>").
225 * @return array|null An array of allowed tags or null if none.
226 */
227 function parseAllowableTags(allowableTags) {
228 6 var tagsArray = [],
229 match;
230
231 6 while ((match = ALLOWED_TAGS_REGEX.exec(allowableTags)) !== null) {
232 6 tagsArray.push(match[1]);
233 }
234
235
return
tagsArray.length !== 0
? tagsArray :
null
;
236 }
237
238 1 module.exports = striptags;
239

helpers/deprecated/enumerate.js

62.50%
16
10
6
Line Lint Hits Source
1 'use strict';
2
3 /**
4 * @deprecate Use {{#for start end (context)}}...{{/for}}
5 */
6 1 const factory = () => {
7 return function(start, end) {
8
const options = arguments[arguments.length - 1];
9 var out = '';
10 var i = start;
11
12 for (i; i <= end; i++) {
13 out = out + options.fn(i);
14 }
15
16 return out + '';
17 };
18 };
19
20 1 module.exports = [{
21 name: 'enumerate',
22 factory: factory,
23 }];
24

helpers/deprecated/equals.js

100%
14
14
0
Line Lint Hits Source
1 'use strict';
2
3 /**
4 * @deprecate Use {{#if val1 '==' val2}}...{{/if}}
5 */
6 1 const factory = () => {
7 return function(val1, val2) {
8 const options = arguments[arguments.length - 1];
9
10 4 if (val1 != val2) {
11 2 return '';
12 }
13
14 2 return options.fn();
15 };
16 };
17
18 1 module.exports = [{
19 name: 'equals',
20 factory: factory,
21 }];
22

helpers/deprecated/getShortMonth.js

97.22%
36
35
1
Line Lint Hits Source
1 'use strict';
2
3 /**
4 * @deprecate Use lang + concat
5 */
6 1 const factory = () => {
7 return function(index) {
8 switch (index) {
9 case 1:
10 1 return 'Jan';
11 case 2:
12 1 return 'Feb';
13 case 3:
14 1 return 'Mar';
15 case 4:
16 1 return 'Apr';
17 case 5:
18 1 return 'May';
19 case 6:
20 1 return 'Jun';
21 case 7:
22 1 return 'Jul';
23 case 8:
24 1 return 'Aug';
25 case 9:
26 1 return 'Sep';
27 case 10:
28 1 return 'Oct';
29 case 11:
30 1 return 'Nov';
31 case 12:
32 1 return 'Dec';
33 }
34
35 return '';
36 };
37 };
38
39 1 module.exports = [{
40 name: 'getShortMonth',
41 factory: factory,
42 }];
43

helpers/deprecated/pick.js

52.38%
21
11
10
Line Lint Hits Source
1 'use strict';
2
3
4 1 const factory = () => {
5 /**
6 * @deprecate
7 */
8 return function(...args) {
9
args.pop();
10 const target = args.shift();
11 const toReturn = {};
12 const paths = args[0];
13 if (paths && Array.isArray(paths)) {
14 paths.forEach((key) => {
15
if (Object.hasOwnProperty.call(target, key)) {
16 toReturn[key] = target[key];
17
}
18 })
19 }
20
21 return toReturn;
22 };
23 };
24
25 1 module.exports = [{
26 name: 'pick',
27 factory: factory,
28 }];
29

helpers/lib/cdnify.js

88.16%
76
67
9
Line Lint Hits Source
1 'use strict';
2
3 // Return a function that can be used to translate paths to cdn paths
4 1 module.exports = globals => {
5 /**
6 * Add CDN base url to the relative path
7 * @param {String} path Relative path
8 * @return {String} Url cdn
9 */
10 return function(path) {
11 const siteSettings = globals.getSiteSettings();
12 57 const themeSettings = globals.getThemeSettings();
13
14 57 const cdnUrl = siteSettings.cdn_url || '';
15 57 const versionId = siteSettings.theme_version_id;
16 57 const editSessionId = siteSettings.theme_session_id;
17 57 const cdnSettings = themeSettings.cdn;
18
19 57 if (path instanceof globals.handlebars.SafeString) {
20 6 path = path.string;
21 }
22
23 57 if (!path) {
24 2 return '';
25 }
26
27 55 if (path.startsWith('https://') || path.startsWith('http://') || path.startsWith('//')) {
28 3 return path;
29 }
30 // This block is to strip leading . and / from path
31 52 if (['.', '/'].includes(path[0])) {
32
for (let i = 0;
i < path.length
; i++) {
33 102 if (path[i] !== '.' && path[i] !== '/') {
34 15 path = path.slice(i);
35 15 break;
36 }
37 }
38 }
39
40 52 let colonIndex = path.indexOf(':');
41
42 52 if (colonIndex !== -1) {
43
while (
colonIndex + 1 < path.length
&& path[colonIndex + 1] === ':') {
44 1 colonIndex++;
45 }
46 22 const protocol = path.slice(0, colonIndex + 1);
47 22 path = path.slice(colonIndex + 1);
48 //This block is to strip leading / from path
49 22 if (path[0] === '/') {
50
for (let i = 1;
i < path.length
; i++) {
51 13 if (path[i] !== '/') {
52 12 path = path.slice(i);
53 12 break;
54 }
55 }
56 }
57
58 22 if (protocol === 'webdav:') {
59 3 return [cdnUrl, 'content', path].join('/');
60 }
61
62 19 if (cdnSettings) {
63 17 const endpointKey = protocol.slice(0, protocol.length - 1);
64 17 if (Object.hasOwnProperty.call(cdnSettings, endpointKey)) {
65 13 if (cdnUrl) {
66 9 return [cdnSettings[endpointKey], path].join('/');
67 }
68
69 4 return ['/assets/cdn', endpointKey, path].join('/');
70 }
71 }
72
73
if (
path[0] !== '/'
) {
74 6 path = '/' + path;
75 }
76
77 6 return path;
78 }
79
80 30 if (!versionId) {
81
if (
path[0] !== '/'
) {
82 5 path = '/' + path;
83 }
84 5 return path;
85 }
86
87
if (
path[0] === '/'
) {
88 path = path.slice(1);
89 }
90
91 25 if (path.startsWith('assets/')) {
92 11 path = path.slice(7);
93 }
94
95
if (
editSessionId
) {
96 return [cdnUrl, 'stencil', versionId, 'e', editSessionId, path].join('/');
97 }
98
99 25 return [cdnUrl, 'stencil', versionId, path].join('/');
100 };
101 };
102

helpers/lib/common.js

98.51%
67
66
1
Line Lint Hits Source
1 'use strict';
2 1 const url = require('url');
3
4 function isValidURL(val) {
5 101 try {
6 101 return url.parse(val).hostname !== null;
7 } catch (e) {
8 return false;
9 }
10 }
11
12 /*
13 * Based on https://github.com/jonschlinkert/get-value/blob/2.0.6/index.js with some enhancements.
14 *
15 * - Performs "hasOwnProperty" checks for safety.
16 * - Now accepts Handlebars.SafeString paths.
17 */
18 function getValue(object, path, globals) {
19 494 let parts;
20
21 // for backwards compatibility
22 494 if (!path) {
23 3 return object;
24 }
25
26 // unwrap Handlebars.SafeString for compatibility with `concat` etc.
27 491 if (globals && globals.handlebars) {
28 40 path = unwrapIfSafeString(globals.handlebars, path);
29 }
30
31 // accept array or string for backwards compatibility
32 491 if (typeof path === 'string') {
33 481 parts = path.split('.');
34 10 } else if (Array.isArray(path)) {
35 7 parts = path;
36 } else {
37 3 let key = String(path);
38 3 return Object.keys(object).indexOf(key) !== -1 ? object[key] : undefined;
39 }
40
41 488 let result = object;
42 488 let prefix = '';
43 for (let key of parts) {
44 820 if (result === undefined || result === null) {
45 3 return undefined;
46 }
47 // preserve handling of trailing backslashes for backwards compatibility
48 817 if (key.slice(-1) === '\\') {
49 4 prefix = prefix + key.slice(0, -1) + '.';
50 4 continue;
51 }
52 813 key = prefix + key;
53 813 if (Object.keys(result).indexOf(key) !== -1) {
54 729 result = result[key];
55 729 prefix = '';
56 } else {
57 84 return;
58 }
59 }
60 401 return result;
61 }
62
63 function unwrapIfSafeString(handlebars, val) {
64 385 if (val instanceof handlebars.SafeString) {
65 12 val = val.toString();
66 }
67 385 return val;
68 }
69
70 1 const maximumPixelSize = 5120;
71
72 /**
73 * Append compression=lossy query parameter to a URL if lossy is true
74 * @param {string} url - The URL to modify
75 * @param {boolean} lossy - Whether to append the lossy compression parameter
76 * @returns {string} - The modified URL
77 */
78 function appendLossyParam(url, lossy) {
79 183 if (!lossy || typeof lossy !== 'boolean') {
80 150 return url;
81 }
82
83 33 const urlObj = new URL(url);
84 33 urlObj.searchParams.set('compression', 'lossy');
85 33 return urlObj.toString();
86 }
87
88 1 module.exports = {
89 isValidURL,
90 getValue,
91 unwrapIfSafeString,
92 maximumPixelSize,
93 appendLossyParam
94 };
95

helpers/lib/fonts.js

96.55%
116
112
4
Line Lint Hits Source
1 'use strict';
2
3 1 const URL = require('url')
4 1 const utils = require('../3p/utils');
5 1 const { ValidationError } = require('../../lib/errors');
6
7 1 const {resourceHintAllowedTypes, addResourceHint, defaultResourceHintState} = require('../lib/resourceHints');
8
9 1 const fontProviders = {
10 'Google': {
11 /**
12 * Parser for Google fonts
13 *
14 * @param {Array} fonts - Array of fonts that might look like
15 * Google_Open+Sans
16 * Google_Open+Sans_400
17 * Google_Open+Sans_400_sans
18 * Google_Open+Sans_400,700_sans
19 * Google_Open+Sans_400,700italic
20 * Google_Open+Sans_400,700italic_sans
21 *
22 * @returns {string}
23 */
24 parser: function (fonts) {
25 var collection = [],
26 familyHash = {};
27
28 8 Object.keys(fonts).forEach(function fontsIterator(key) {
29 const font = fonts[key];
30 49 var split = font.split('_'),
31 familyKey = split[1], // Eg: Open+Sans
32 weights = split[2]; // Eg: 400,700italic
33
34 49 if (utils.isEmpty(familyKey)) {
35 6 return;
36 }
37
38 43 if (utils.isUndefined(weights)) {
39 13 weights = '';
40 }
41
42 43 if (!utils.isArray(familyHash[familyKey])) {
43 33 familyHash[familyKey] = [];
44 }
45
46 43 weights = weights.split(',');
47
48 43 familyHash[familyKey].push(weights);
49 43 familyHash[familyKey] = [...new Set(familyHash[familyKey].flat())]
50 });
51
52 8 Object.keys(familyHash).forEach(function fontHashIterator(family) {
53 const weights = familyHash[family];
54 33 collection.push(family + ':' + weights.join(','));
55 });
56
57 8 return collection;
58 },
59
60 buildLink: function (fonts, fontDisplay) {
61 const displayTypes = ['auto', 'block', 'swap', 'fallback', 'optional'];
62 6 fontDisplay = displayTypes.includes(fontDisplay) ? fontDisplay : 'swap';
63 6 const uri = `https://fonts.googleapis.com/css?family=${fonts.join('|')}&display=${fontDisplay}`
64 6 try {
65 6 const url = URL.parse(uri);
66 6 return `<link href="${url.format()}" rel="stylesheet">`;
67 } catch (error) {
68 throw new ValidationError(`Invalid URL [${uri}]. Check configured fonts.`)
69 }
70 },
71
72 buildFontLoaderConfig: function (fonts) {
73 function replaceSpaces(font) {
74 6 return font.split('+').join(' ');
75 }
76
77 1 return {
78 google: {
79 families: fonts.map(font => replaceSpaces(font)),
80 }
81 };
82 },
83
84 generateResourceHints: function (globals, fonts, fontDisplay) {
85 const displayTypes = ['auto', 'block', 'swap', 'fallback', 'optional'];
86 6 fontDisplay = displayTypes.includes(fontDisplay) ? fontDisplay : 'swap';
87 6 const path = `https://fonts.googleapis.com/css?family=${fonts.join('|')}&display=${fontDisplay}`;
88 6 try {
89 6 addResourceHint(
90 globals,
91 path,
92 defaultResourceHintState,
93 resourceHintAllowedTypes.resourceHintStyleType
94 );
95 } catch (e) {
96 console.info(`EarlyHint generation failed while generating fonts collection.`, e);
97 }
98 }
99 },
100 };
101
102 /**
103 * Get collection of fonts used in theme settings.
104 *
105 * @param {string} format The desired return value. If format == 'providerLists', return an object with provider names for keys
106 * and a list of fonts in the provider format as values, suitable for use with Web Font Loader. If format == 'linkElements',
107 * return a string containing <link> elements to be directly inserted into the page. If format == 'webFontLoaderConfig', return an
108 * object that can be used to configure Web Font Loader.
109 * @param {Object} themeSettings Object containing theme settings.
110 * @param {Object} handlebars The handlebars instance.
111 * @param {Object} options an optional object for additional configuration details
112 * @returns {Object.<string, Array>|string}
113 */
114 1 module.exports = function (format, themeSettings, handlebars, options) {
115
116 const collectedFonts = {};
117 8 Object.keys(themeSettings).forEach(function (key) {
118 //check that -font is on end of string but not start of string
119 const fontKeySuffix = '-font';
120 54 if (!key.endsWith(fontKeySuffix)) {
121 5 return;
122 }
123
124 49 const splits = themeSettings[key].split('_');
125 49 const provider = splits[0];
126
127
if (
typeof fontProviders[provider] === 'undefined'
) {
128 return;
129 }
130
131 49 if (typeof collectedFonts[provider] === 'undefined') {
132 8 collectedFonts[provider] = [];
133 }
134
135 49 collectedFonts[provider].push(themeSettings[key]);
136 });
137
138 // Parse font strings based on provider
139 8 const parsedFonts = {};
140 8 Object.keys(collectedFonts).forEach(function (key) {
141 parsedFonts[key] = fontProviders[key].parser(collectedFonts[key]);
142 });
143
144 // Format output based on requested format
145 8 switch (format) {
146 case 'linkElements':
147 6 const formattedFonts = {};
148 6 Object.keys(parsedFonts).forEach(function (key) {
149 fontProviders[key].generateResourceHints(options.globals, parsedFonts[key], options.fontDisplay);
150 6 formattedFonts[key] = fontProviders[key].buildLink(parsedFonts[key], options.fontDisplay);
151 });
152 6 return new handlebars.SafeString(Object.values(formattedFonts).join(''));
153
154 case 'webFontLoaderConfig':
155 // Build configs
156 1 const configs = {};
157 1 Object.keys(parsedFonts).forEach(function (key) {
158 configs[key] = fontProviders[key].buildFontLoaderConfig(parsedFonts[key]);
159 });
160
161 // Merge them
162 1 const fontLoaderConfig = {};
163 1 Object.values(configs).forEach((config) => {
164 Object.assign(fontLoaderConfig, config);
165 });
166 1 return fontLoaderConfig;
167
168 case 'providerLists':
169 default:
170 1 return parsedFonts;
171 }
172 }
173

helpers/lib/getObjectStorageImage.js

95.65%
46
44
2
Line Lint Hits Source
1 'use strict';
2 1 const utils = require('../3p/utils');
3 1 const common = require('./common');
4
5 1 const { ValidationError } = require('../../lib/errors');
6
7 1 const srcsets = {
8 '80w': '80w',
9 '160w': '160w',
10 '320w': '320w',
11 '640w': '640w',
12 '960w': '960w',
13 '1280w': '1280w',
14 '1920w': '1920w',
15 '2560w': '2560w',
16 };
17
18 function getObjectStorageImage(handlebars, cdnUrl, source, path, options) {
19 34 if (!utils.isString(path) || common.isValidURL(path)) {
20 4 throw new ValidationError("Invalid image path - please use a filename or folder path starting from the appropriate folder");
21 }
22
23 // Return original image if there are no arguments
24 30 let size = 'original';
25
const lossy =
options
&&
options.hash
&& options.hash.lossy;
26
27 30 if (Number.isInteger(options.hash['width'])) {
28 12 if (Number.isInteger(options.hash['height'])) {
29 // Return image of specified size
30 6 size = `${options.hash['width']}x${options.hash['height']}`
31 } else {
32 // Return image of specified width
33 6 size = `${options.hash['width']}w`
34 }
35 }
36
37 30 const baseUrl = `${cdnUrl}/images/stencil/${size}/${source}/${path}`;
38 30 const finalUrl = common.appendLossyParam(baseUrl, lossy);
39 30 return new handlebars.SafeString(finalUrl);
40 }
41
42 function getObjectStorageImageSrcset(handlebars, cdnUrl, source, path, options) {
43 14 if (!utils.isString(path) || common.isValidURL(path)) {
44 4 throw new ValidationError("Invalid image path - please use a filename or folder path starting from the appropriate folder");
45 }
46
47
const lossy =
options
&&
options.hash
&& options.hash.lossy;
48
49 10 return new handlebars.SafeString(Object.keys(srcsets).map(descriptor => {
50 const baseUrl = `${cdnUrl}/images/stencil/${srcsets[descriptor]}/${source}/${path}`;
51 80 const finalUrl = common.appendLossyParam(baseUrl, lossy);
52 80 return ([`${finalUrl} ${descriptor}`].join(' '));
53 }).join(', '));
54 }
55
56 1 module.exports = {
57 getObjectStorageImage,
58 getObjectStorageImageSrcset
59 };
60

helpers/lib/resourceHints.js

95.59%
68
65
3
Line Lint Hits Source
1 1 const utils = require("../3p/utils");
2 1 const URL = require('url');
3
4 1 const { ValidationError } = require('../../lib/errors');
5
6 1 const resourceHintsLimit = 50;
7
8 1 const preloadResourceHintState = 'preload';
9 1 const preconnectResourceHintState = 'preconnect';
10 1 const prerenderResourceHintState = 'prerender';
11 1 const dnsPrefetchResourceHintState = 'dns-prefetch';
12 1 const resourceHintStates = [preloadResourceHintState, preconnectResourceHintState, prerenderResourceHintState, dnsPrefetchResourceHintState];
13
14 1 const resourceHintFontType = 'font';
15 1 const resourceHintStyleType = 'style';
16 1 const resourceHintScriptType = 'script';
17 1 const resourceHintDocumentType = 'document';
18 1 const resourceHintTypes = [resourceHintStyleType, resourceHintFontType, resourceHintScriptType, resourceHintDocumentType];
19
20 1 const noCors = 'no';
21 1 const anonymousCors = 'anonymous';
22 1 const useCredentialsCors = 'use-credentials';
23 1 const allowedCors = [noCors, anonymousCors, useCredentialsCors];
24
25 /**
26 * @param {string} path - The uri to the resource.
27 * @param {string} rel - any of [preload, preconnect, prerender, dns-prefetch]
28 * @param {string} type? - (as attr in HTML link tag) any of [style, font, script,document] If an invalid value is provided, property won't be included
29 * @param {string} cors? - (crossorigin attr in HTML tag) any of [no, anonymous, use-credentials] defaults to no when no value is provided
30 */
31 function addResourceHint(globals, path, rel, type, cors) {
32
33 40 if (!utils.isString(path)) {
34 4 throw new ValidationError('Invalid path provided. path should be a non empty string');
35 }
36 36 path = path.trim();
37 36 try {
38 36 path = URL.parse(path).format();
39 } catch (error) {
40 throw new ValidationError(`Invalid value (resource URL) provided: ${path}`)
41 }
42
43
if (
!utils.isString(rel)
|| !resourceHintStates.includes(rel)) {
44 2 throw new ValidationError(`resourceHint attribute received invalid value. Valid values are: ${resourceHintStates}`);
45 }
46
47 34 if (typeof globals.resourceHints === 'undefined') {
48 1 globals.resourceHints = [];
49 }
50
51 34 let index = globals.resourceHints.findIndex(({ src }) => path === src);
52 34 if (index >= 0) {
53 6 return path;
54 }
55
56 28 if (globals.resourceHints.length >= resourceHintsLimit) {
57 1 console.warn(`Resource hint for [${path}] due to the max limit of allowed hints was reached.`);
58 1 return path;
59 }
60
61 27 let hint = {src: path, state: rel};
62
63 27 if (utils.isString(type) && resourceHintTypes.includes(type)) {
64 24 hint.type = type;
65 }
66
67 27 if (utils.isString(cors) && !allowedCors.includes(cors)) {
68 throw new ValidationError(`Invalid cors value provided. Valid values are: ${allowedCors}`);
69 27 } else if (!utils.isString(cors)) {
70 11 cors = noCors;
71 }
72 27 hint.cors = cors;
73
74 27 globals.resourceHints.push(hint);
75
76 27 return path;
77 }
78
79 1 module.exports = {
80 resourceHintsLimit,
81 defaultResourceHintState: preloadResourceHintState,
82 addResourceHint,
83 resourceHintAllowedTypes: { resourceHintStyleType, resourceHintFontType, resourceHintScriptType },
84 resourceHintAllowedCors: { noCors, anonymousCors, useCredentialsCors },
85 resourceHintAllowedStates: {
86 preloadResourceHintState,
87 preconnectResourceHintState,
88 prerenderResourceHintState,
89 dnsPrefetchResourceHintState
90 }
91 };
92

lib/appError.js

100%
10
10
0
Line Lint Hits Source
1 'use strict';
2
3 class AppError extends Error {
4 constructor(message, details) {
5 // Calling parent constructor of base Error class.
6 super(message);
7
8 // Saving class name in the property of our custom error as a shortcut.
9 57 this.name = this.constructor.name;
10
11 // Capturing stack trace, excluding constructor call from it.
12 57 Error.captureStackTrace(this, this.constructor);
13
14 // Store extra details for caller to diagnose error
15 57 this.details = details || {};
16 }
17 };
18
19 1 module.exports = AppError;
20

lib/errors.js

100%
17
17
0
Line Lint Hits Source
1 1 const AppError = require('./appError');
2
3 class CompileError extends AppError {}; // Error compiling template
4 class FormatError extends AppError {}; // Error restoring precompiled template
5 class RenderError extends AppError {}; // Error rendering template
6 class DecoratorError extends AppError {}; // Error applying decorator
7 class TemplateNotFoundError extends AppError {}; // Template not registered
8 class ValidationError extends Error {};
9 class PrecompileError extends Error {};
10
11 1 module.exports = {
12 CompileError,
13 FormatError,
14 RenderError,
15 DecoratorError,
16 TemplateNotFoundError,
17 ValidationError,
18 PrecompileError
19 };
20

spec/helpers.js

100%
189
189
0
Line Lint Hits Source
1 'use strict';
2
3 1 const Code = require('code');
4 1 const Lab = require('lab');
5 1 const lab = exports.lab = Lab.script();
6 1 const expect = Code.expect;
7 1 const it = lab.it;
8 1 const describe = lab.describe;
9 1 const beforeEach = lab.beforeEach;
10
11 1 describe('helper registration', () => {
12 let helpers;
13
14 1 beforeEach(done => {
15 helpers = require('../helpers');
16 1 done();
17 });
18
19 1 it('loads all the helpers', done => {
20 const expectedHelpers = [
21 'all',
22 'any',
23 'assignVar',
24 'block',
25 'cdn',
26 'compare',
27 'concat',
28 'contains',
29 'decrementVar',
30 'dynamicComponent',
31 'encodeHtmlEntities',
32 'for',
33 'get',
34 'getContentImage',
35 'getContentImageSrcset',
36 'getFontLoaderConfig',
37 'getFontsCollection',
38 'getImage',
39 'getImageManagerImage',
40 'getImageManagerImageSrcset',
41 'getImageSrcset',
42 'getImageSrcset1x2x',
43 'getObject',
44 'getVar',
45 'helperMissing',
46 'if',
47 'incrementVar',
48 'inject',
49 'join',
50 'jsContext',
51 'json',
52 'lang',
53 'langJson',
54 'limit',
55 'moment',
56 'money',
57 'multiConcat',
58 'nl2br',
59 'occurrences',
60 'option',
61 'or',
62 'partial',
63 'pluck',
64 'pre',
65 'region',
66 'replace',
67 'resourceHints',
68 'setURLQueryParam',
69 'snippet',
70 'stripQuerystring',
71 'strReplace',
72 'stylesheet',
73 'after',
74 'arrayify',
75 'before',
76 'eachIndex',
77 'filter',
78 'first',
79 'forEach',
80 'inArray',
81 'isArray',
82 'last',
83 'lengthEqual',
84 'map',
85 'some',
86 'sort',
87 'sortBy',
88 'withAfter',
89 'withBefore',
90 'withFirst',
91 'withLast',
92 'withSort',
93 'isEmpty',
94 'iterate',
95 'length',
96 'and',
97 'gt',
98 'gte',
99 'has',
100 'eq',
101 'ifEven',
102 'ifNth',
103 'ifOdd',
104 'is',
105 'isnt',
106 'lt',
107 'lte',
108 'neither',
109 'unlessEq',
110 'unlessGt',
111 'unlessLt',
112 'unlessGteq',
113 'unlessLteq',
114 'ellipsis',
115 'sanitize',
116 'ul',
117 'ol',
118 'thumbnailImage',
119 'inflect',
120 'ordinalize',
121 'markdown',
122 'add',
123 'subtract',
124 'divide',
125 'multiply',
126 'floor',
127 'ceil',
128 'round',
129 'sum',
130 'avg',
131 'default',
132 'noop',
133 'withHash',
134 'addCommas',
135 'phoneNumber',
136 'random',
137 'toAbbr',
138 'toExponential',
139 'toFixed',
140 'toFloat',
141 'toInt',
142 'toPrecision',
143 'extend',
144 'forIn',
145 'forOwn',
146 'toPath',
147 'hasOwn',
148 'isObject',
149 'merge',
150 'JSONparseSafe',
151 'JSONparse',
152 'JSONstringify',
153 'camelcase',
154 'capitalize',
155 'capitalizeAll',
156 'center',
157 'chop',
158 'dashcase',
159 'dotcase',
160 'hyphenate',
161 'isString',
162 'lowercase',
163 'pascalcase',
164 'pathcase',
165 'plusify',
166 'reverse',
167 'sentence',
168 'snakecase',
169 'split',
170 'startsWith',
171 'titleize',
172 'trim',
173 'uppercase',
174 'encodeURI',
175 'decodeURI',
176 'urlResolve',
177 'urlParse',
178 'stripProtocol',
179 'toLowerCase',
180 'truncate',
181 'unless',
182 'enumerate',
183 'equals',
184 'getShortMonth',
185 'pick',
186 'earlyHint',
187 'nonce',
188 'typeof'
189 ].sort();
190
191 1 expect(helpers.map(helper => helper.name).sort()).to.be.equal(expectedHelpers);
192 1 done();
193 });
194 });
195

spec/index.js

99.60%
499
497
2
Line Lint Hits Source
1 'use strict';
2
3 1 const Sinon = require('sinon');
4 1 const Code = require('code');
5 1 const Lab = require('lab');
6 1 const lab = exports.lab = Lab.script();
7 1 const expect = Code.expect;
8 1 const it = lab.it;
9 1 const describe = lab.describe;
10 1 const beforeEach = lab.beforeEach;
11 1 const afterEach = lab.afterEach;
12
13 1 const helpers = require('./spec-helpers');
14 1 const Handlebars = require('handlebars');
15 1 const HandlebarsRenderer = require('../index');
16
17 1 describe('switching handlebars versions', () => {
18 it('defaults to v3', done => {
19 const renderer = new HandlebarsRenderer();
20 1 expect(renderer.handlebars.VERSION.substring(0, 1)).to.equal('3');
21 1 done();
22 });
23
24 1 it('can load v3', done => {
25 const renderer = new HandlebarsRenderer({}, {}, 'v3');
26 1 expect(renderer.handlebars.VERSION.substring(0, 1)).to.equal('3');
27 1 done();
28 });
29
30 1 it('can load v4', done => {
31 const renderer = new HandlebarsRenderer({}, {}, 'v4');
32 1 expect(renderer.handlebars.VERSION.substring(0, 1)).to.equal('4');
33 1 done();
34 });
35 });
36
37 1 describe('helper registration', () => {
38 let renderer;
39
40 1 beforeEach(done => {
41 renderer = new HandlebarsRenderer();
42 1 done();
43 });
44
45 1 it('loads the helpers and registers them with handlebars', done => {
46 // Handlebars loads some default helpers, so let's find out how many
47 const handlebars = Handlebars.create();
48 1 const defaultHelpers = Object.keys(handlebars.helpers).length;
49 1 expect(defaultHelpers).to.be.greaterThan(0);
50
51 // We already test that the expected helpers load properly in the helpers spec,
52 // so here let's just test that we register extra helpers above and beyond the defaults.
53 1 expect(Object.keys(renderer.handlebars.helpers).length).to.be.greaterThan(defaultHelpers);
54 1 done();
55 });
56 });
57
58 1 describe('helper context', () => {
59 let renderer, siteSettings, themeSettings;
60
61 1 beforeEach(done => {
62 renderer = new HandlebarsRenderer();
63 10 done();
64 });
65
66 1 it('puts empty site settings into the helper context when not provided', done => {
67 expect(renderer.helperContext.getSiteSettings()).to.equal({});
68 1 done();
69 });
70
71 1 it('puts site settings into the helper context when provided', done => {
72 siteSettings = { foo: 'bar' };
73 1 renderer = new HandlebarsRenderer(siteSettings);
74 1 expect(renderer.helperContext.getSiteSettings().foo).to.equal('bar');
75 1 done();
76 });
77
78 1 it('puts site settings into the helper context when provided after construction', done => {
79 siteSettings = { foo: 'bar' };
80 1 renderer = new HandlebarsRenderer();
81 1 renderer.setSiteSettings(siteSettings);
82 1 expect(renderer.helperContext.getSiteSettings().foo).to.equal('bar');
83 1 done();
84 });
85
86 1 it('puts empty theme settings into the helper context when not provided', done => {
87 expect(renderer.helperContext.getThemeSettings()).to.equal({});
88 1 done();
89 });
90
91 1 it('puts theme settings into the helper context when provided', done => {
92 themeSettings = { foo: 'bar' };
93 1 renderer = new HandlebarsRenderer({}, themeSettings);
94 1 expect(renderer.helperContext.getThemeSettings().foo).to.equal('bar');
95 1 done();
96 });
97
98 1 it('puts theme settings into the helper context when provided after construction', done => {
99 themeSettings = { foo: 'bar' };
100 1 renderer = new HandlebarsRenderer();
101 1 renderer.setThemeSettings(themeSettings);
102 1 expect(renderer.helperContext.getThemeSettings().foo).to.equal('bar');
103 1 done();
104 });
105
106 1 it('puts translator accessor into the helper context', done => {
107 const translator = { foo: 'bar' };
108 1 renderer.setTranslator(translator);
109 1 expect(renderer.helperContext.getTranslator()).to.equal(translator);
110 1 done();
111 });
112
113 1 it('puts page content accessor into the helper context', done => {
114 const content = { foo: 'bar' };
115 1 renderer.setContent(content);
116 1 expect(renderer.helperContext.getContent()).to.equal(content);
117 1 done();
118 });
119
120 1 it('puts handlebars environment into the helper context', done => {
121 expect(renderer.helperContext.handlebars).to.not.be.undefined();
122 1 expect(renderer.helperContext.handlebars.helpers).to.not.be.empty();
123 1 done();
124 });
125
126 1 it('puts a handle to global storage into the helper context', done => {
127 expect(renderer.helperContext.storage).to.not.be.undefined();
128 1 done();
129 });
130 });
131
132 1 describe('addTemplates', () => {
133 let renderer, sandbox;
134 1 const templates = {
135 'foo': '{{bar}}',
136 'baz': '{{bat}}',
137 };
138
139 1 beforeEach(done => {
140 sandbox = Sinon.createSandbox();
141 8 renderer = new HandlebarsRenderer();
142 8 done();
143 });
144
145 1 afterEach(done => {
146 sandbox.restore();
147 8 done();
148 });
149
150 1 it('registers preprocessed templates with handlebars', done => {
151 const processor = renderer.getPreProcessor();
152 1 renderer.addTemplates(processor(templates));
153 1 expect(renderer.handlebars.partials['foo']).to.not.be.undefined();
154 1 expect(renderer.handlebars.partials['baz']).to.not.be.undefined();
155 1 done();
156 });
157
158 1 it('registers raw templates with handlebars', done => {
159 renderer.addTemplates(templates);
160 1 expect(renderer.handlebars.partials['foo']).to.not.be.undefined();
161 1 expect(renderer.handlebars.partials['baz']).to.not.be.undefined();
162 1 done();
163 });
164
165 1 it('registers a mix of preprocessed and raw templates with handlebars', done => {
166 const processor = renderer.getPreProcessor();
167 1 const processedTemplates = processor(templates);
168 1 renderer.addTemplates(Object.assign({}, processedTemplates, { 'bat': '{{bob}}' }));
169 1 expect(renderer.handlebars.partials['foo']).to.not.be.undefined();
170 1 expect(renderer.handlebars.partials['baz']).to.not.be.undefined();
171 1 expect(renderer.handlebars.partials['bat']).to.not.be.undefined();
172 1 done();
173 });
174
175 1 it('throws FormatError if templates were precompiled with the wrong compiler version', done => {
176 const processor = renderer.getPreProcessor();
177 1 const processedTemplates = processor(templates);
178 1 processedTemplates['baz'] = processedTemplates['baz'].replace(/\[6,">= 2.0.0-beta.1"\]/, '[7,">= 4.0"]');
179 1 try {
180 1 renderer.addTemplates(processedTemplates);
181 } catch(e) {
182 1 expect(e instanceof HandlebarsRenderer.errors.FormatError).to.be.true();
183 }
184 1 done();
185 });
186
187 1 it('preProcessor throws CompileError when given bad templates', done => {
188 const processor = renderer.getPreProcessor();
189 1 try {
190 1 processor(Object.assign({}, templates, { 'bat': '{{' }));
191 } catch(e) {
192 1 expect(e instanceof HandlebarsRenderer.errors.CompileError).to.be.true();
193 1 expect(e.details.path).to.equal('bat');
194 }
195 1 done();
196 });
197
198 1 it('can render using registered partials', done => {
199 const processor = renderer.getPreProcessor();
200 1 renderer.addTemplates(processor(templates));
201 1 renderer.render('baz', { bat: '123' }).then(result => {
202 expect(result).to.equal('123');
203 1 done();
204 });
205 });
206
207 1 it('isTemplateLoaded tells you if a template has been loaded', done => {
208 const processor = renderer.getPreProcessor();
209 1 renderer.addTemplates(processor(templates));
210 1 expect(renderer.isTemplateLoaded('baz')).to.be.true();
211 1 done();
212 });
213
214 1 it('does not override templates if called twice', done => {
215 const newTemplates = {
216 'baz': 'no variables',
217 };
218
219 1 const processor = renderer.getPreProcessor();
220 1 renderer.addTemplates(processor(templates));
221 1 renderer.addTemplates(processor(newTemplates));
222 1 renderer.render('baz', { bat: '123' }).then(result => {
223 expect(result).to.equal('123');
224 1 done();
225 });
226 });
227 });
228
229 1 describe('render', () => {
230 let renderer, sandbox;
231 1 const templates = {
232 'foo': '{{bar}}',
233 'capitalize_foo': '{{capitalize bar}}',
234 'with_locale': '{{locale_name}}',
235 'with_template': '{{template}}',
236 };
237 1 const context = {
238 bar: 'baz'
239 };
240
241 1 beforeEach(done => {
242 sandbox = Sinon.createSandbox();
243 9 renderer = new HandlebarsRenderer();
244 9 const processor = renderer.getPreProcessor();
245 9 renderer.addTemplates(processor(templates));
246 9 done();
247 });
248
249 1 afterEach(done => {
250 sandbox.restore();
251 9 done();
252 });
253
254 1 it('can render using registered partials', done => {
255 renderer.render('foo', context).then(result => {
256 expect(result).to.equal('baz');
257 1 done();
258 });
259 });
260
261 1 it('renders without a context', done => {
262 renderer.render('foo').then(result => {
263 expect(result).to.equal('');
264 1 done();
265 });
266 });
267
268 1 it('sets the locale in the context if a translator is supplied', done => {
269 const translator = {
270 getLocale: () => 'en',
271 };
272 1 renderer.setTranslator(translator);
273 1 renderer.render('with_locale', context).then(result => {
274 expect(result).to.equal('en');
275 1 done();
276 });
277 });
278
279 1 it('sets the template path in the context', done => {
280 renderer.render('with_template', context).then(result => {
281 expect(result).to.equal('with_template');
282 1 done();
283 });
284 });
285
286 1 it('applies decorators', done => {
287 renderer.addDecorator(output => {
288 return `<wrap>${output}</wrap>`;
289 });
290 1 renderer.render('foo', context).then(result => {
291 expect(result).to.equal('<wrap>baz</wrap>');
292 1 done();
293 });
294 });
295
296 1 it('can use helpers', done => {
297 renderer.render('capitalize_foo', context).then(result => {
298 expect(result).to.equal('Baz');
299 1 done();
300 });
301 });
302
303 1 it('throws TemplateNotFound if missing template', done => {
304 renderer.render('nonexistent-template', context).catch(e => {
305 expect(e instanceof HandlebarsRenderer.errors.TemplateNotFoundError).to.be.true();
306 1 done();
307 });
308 });
309
310 1 it('throws RenderError if given bad template', done => {
311 renderer.addTemplates({'bad-template': '{{'});
312 1 renderer.render('bad-template', context).catch(e => {
313 expect(e instanceof HandlebarsRenderer.errors.RenderError).to.be.true();
314 1 done();
315 });
316 });
317
318 1 it('throws DecoratorError if decorator fails', done => {
319 renderer.addDecorator(output => {
320 throw Error();
321 });
322
323 1 renderer.render('foo', context).catch(e => {
324 expect(e instanceof HandlebarsRenderer.errors.DecoratorError).to.be.true();
325 1 done();
326 });
327 });
328 });
329
330 1 describe('renderString', () => {
331 let renderer, sandbox;
332 1 const context = {
333 bar: 'baz'
334 };
335
336 1 beforeEach(done => {
337 sandbox = Sinon.createSandbox();
338 5 renderer = new HandlebarsRenderer();
339 5 done();
340 });
341
342 1 afterEach(done => {
343 sandbox.restore();
344 5 done();
345 });
346
347 1 it('renders the given template with the given context', done => {
348 renderer.renderString('<foo>{{bar}}</foo>', context).then(result => {
349 expect(result).to.equal('<foo>baz</foo>');
350 1 done();
351 });
352 });
353
354 1 it('can use helpers', done => {
355 renderer.renderString('<foo>{{capitalize bar}}</foo>', context).then(result => {
356 expect(result).to.equal('<foo>Baz</foo>');
357 1 done();
358 });
359 });
360
361 1 it('renders without a context', done => {
362 renderer.renderString('foo', context).then(result => {
363 expect(result).to.equal('foo');
364 1 done();
365 });
366 });
367
368 1 it('throws CompileError if given a non-string template', done => {
369 renderer.renderString(helpers.randomInt(), context).catch(e => {
370 expect(e instanceof HandlebarsRenderer.errors.CompileError).to.be.true();
371 1 done();
372 });
373 });
374
375 1 it('throws PrecompileError if given malformed template', done => {
376 renderer.renderString('{{', context).catch(e => {
377 expect(e instanceof HandlebarsRenderer.errors.PrecompileError).to.be.true();
378 1 done();
379 });
380 });
381 });
382
383 1 describe('errors', () => {
384 let sandbox;
385
386 1 beforeEach(done => {
387 sandbox = Sinon.createSandbox();
388 1 done();
389 });
390
391 1 afterEach(done => {
392 sandbox.restore();
393 1 done();
394 });
395
396 1 it('has an accessor to get custom error classes', done => {
397 expect(HandlebarsRenderer.errors).to.not.be.empty();
398 1 expect(HandlebarsRenderer.errors.CompileError.prototype instanceof Error).to.be.true();
399 1 expect(HandlebarsRenderer.errors.FormatError.prototype instanceof Error).to.be.true();
400 1 expect(HandlebarsRenderer.errors.RenderError.prototype instanceof Error).to.be.true();
401 1 expect(HandlebarsRenderer.errors.DecoratorError.prototype instanceof Error).to.be.true();
402 1 expect(HandlebarsRenderer.errors.TemplateNotFoundError.prototype instanceof Error).to.be.true();
403 1 done();
404 });
405 });
406
407 1 describe('logging', () => {
408 let renderer, logger, sandbox;
409 1 let consoleLogCopy = console.log;
410 1 let consoleInfoCopy = console.info;
411 1 let consoleErrorCopy = console.error;
412 1 let consoleWarnCopy = console.warn;
413 1 const context = {
414 bar: 'baz'
415 };
416
417 1 beforeEach(done => {
418 sandbox = Sinon.createSandbox();
419 12 logger = {
420 info: Sinon.fake(),
421 warn: Sinon.fake(),
422 error: Sinon.fake(),
423 };
424 12 console = {
425 log: Sinon.fake(),
426 };
427 12 done();
428 });
429
430 1 afterEach(done => {
431 console.log = consoleLogCopy;
432 12 console.info = consoleInfoCopy;
433 12 console.error = consoleErrorCopy;
434 12 console.warn = consoleWarnCopy;
435 12 sandbox.restore();
436 12 done();
437 });
438
439 1 it('log helper uses given logger', done => {
440 renderer = new HandlebarsRenderer({}, {}, 'v3', logger);
441 1 renderer.renderString('{{log bar}}', context).then(() => {
442 expect(logger.info.called).to.equal(false);
443 1 done();
444 });
445 });
446
447 1 it('log helper should not be called, when log level = error', done => {
448 renderer = new HandlebarsRenderer({}, {}, 'v4', logger, 'error');
449 1 renderer.renderString('{{log bar}}', context).then(() => {
450 expect(logger.info.called).to.equal(false);
451 1 done();
452 });
453 });
454
455 1 it('log helper should not be called, when log level = warning', done => {
456 renderer = new HandlebarsRenderer({}, {}, 'v4', logger, 'warning');
457 1 renderer.renderString('{{log bar}}', context).then(() => {
458 expect(logger.info.called).to.equal(false);
459 1 done();
460 });
461 });
462
463
464 1 it('console log helper uses given logger', done => {
465 renderer = new HandlebarsRenderer({}, {}, 'v4', console);
466 1 renderer.renderString('{{log bar}}', context).then(() => {
467 expect(console.log.calledWith('baz')).to.equal(true);
468 1 done();
469 });
470 });
471
472 1 it('console log helper should not be called, when log level = error', done => {
473 renderer = new HandlebarsRenderer({}, {}, 'v4', console, 'error');
474 1 renderer.renderString('{{log bar}}', context).then(() => {
475 expect(console.log.called).to.equal(false);
476 1 done();
477 });
478 });
479
480 1 it('console log helper should not be called, when log level = warning', done => {
481 renderer = new HandlebarsRenderer({}, {}, 'v4', console, 'warning');
482 1 renderer.renderString('{{log bar}}', context).then(() => {
483 expect(console.log.called).to.equal(false);
484 1 done();
485 });
486 });
487
488 1 it('should override default console.log', done => {
489 renderer = new HandlebarsRenderer({}, {}, 'v4', logger);
490 1 console.log('test');
491 1 expect(logger.info.calledWith('test')).to.equal(true);
492 1 done();
493 });
494
495 1 it('should override default console.info', done => {
496 renderer = new HandlebarsRenderer({}, {}, 'v4', logger);
497 1 console.info('info');
498 1 expect(logger.info.calledWith('info')).to.equal(true);
499 1 done();
500 });
501
502 1 it('should override default console.error', done => {
503 renderer = new HandlebarsRenderer({}, {}, 'v4', logger);
504 1 console.error('error');
505 1 expect(logger.error.calledWith('error')).to.equal(true);
506 1 done();
507 });
508
509 1 it('should override default console.warn', done => {
510 renderer = new HandlebarsRenderer({}, {}, 'v4', logger);
511 1 console.warn('test');
512 1 expect(logger.warn.calledWith('test')).to.equal(true);
513 1 done();
514 });
515
516 1 it('should check that property access denied message is set as info', async () => {
517 const renderer = new HandlebarsRenderer({}, {}, 'v4', logger);
518 1 const result = await renderer.renderString("{{aString.trim}}", { aString: " abc " });
519 1 expect(result).to.equal("");
520 1 expect(logger.info.calledWith('Handlebars: Access has been denied to resolve the property "trim" because it is not an "own property" of its parent.\n' + 'You can add a runtime option to disable the check or this warning:\n' + 'See https://handlebarsjs.com/api-reference/runtime-options.html#options-to-control-prototype-access for details'))
521 .to.equal(true);
522
523 });
524
525 1 it('should check that property access denied message is set as error', async () => {
526 console.error = Sinon.fake();
527 1 const renderer = new HandlebarsRenderer({}, {}, 'v4', console);
528 1 const result = await renderer.renderString("{{aString.toLowerCase}}", { aString: " abc " });
529 1 expect(result).to.equal("");
530 1 expect(console.error.calledWith('Handlebars: Access has been denied to resolve the property "toLowerCase" because it is not an "own property" of its parent.\n' + 'You can add a runtime option to disable the check or this warning:\n' + 'See https://handlebarsjs.com/api-reference/runtime-options.html#options-to-control-prototype-access for details'))
531 .to.equal(true);
532 });
533 });
534
535 // STRF-12276
536 1 describe('object manipulation fix', () => {
537 let sandbox, logger;
538 1 let consoleErrorCopy = console.error;
539 1 const templateString = `{{#JSONparse}} '{"a":"b"}'{{/JSONparse}}`;
540
541 1 beforeEach(done => {
542 sandbox = Sinon.createSandbox();
543 2 logger = {
544 info: Sinon.fake(),
545 warn: Sinon.fake(),
546 error: Sinon.fake(),
547 };
548 2 console = {
549 log: console.log,
550 error: Sinon.fake(),
551 };
552 2 done();
553 });
554
555 1 afterEach(done => {
556 console.error = consoleErrorCopy;
557 2 sandbox.restore();
558 2 done();
559 });
560
561 1 it('shouldnt print when use renderString', async () => {
562 const renderer = new HandlebarsRenderer({}, {}, 'v4', logger);
563 1 try {
564 1 await renderer.renderString(templateString, {});
565 expect(logger.error.called()).to.equal(false);
566 } catch (e) {
567 1 expect(e instanceof HandlebarsRenderer.errors.RenderError).to.be.true();
568 }
569 });
570
571 1 it('shouldnt print when use render', async () => {
572 const renderer = new HandlebarsRenderer({}, {}, 'v4', logger);
573 1 try {
574 1 const templates = {'foo': templateString };
575
576 1 const processor = renderer.getPreProcessor();
577 1 renderer.addTemplates(processor(templates));
578 1 await renderer.render('foo', {});
579 expect(logger.error.called()).to.equal(false);
580 } catch (e) {
581 1 expect(e instanceof HandlebarsRenderer.errors.RenderError).to.be.true();
582 }
583 });
584 });
585
586
587

spec/spec-helpers.js

91.07%
56
51
5
Line Lint Hits Source
1 1 const Crypto = require('crypto');
2 1 const Renderer = require('../index');
3 1 const expect = require('code').expect;
4
5 function buildRenderer(siteSettings, themeSettings, templates, hbVersion, requestParams) {
6 623 siteSettings = siteSettings || {};
7 623 themeSettings = themeSettings || {};
8 623 hbVersion = hbVersion || 'v3';
9 623 requestParams = requestParams || {};
10 623 const renderer = new Renderer(siteSettings, themeSettings, hbVersion, console, 'info', requestParams);
11
12 // Register templates
13 623 if (typeof templates !== 'undefined') {
14 22 templates = renderer.getPreProcessor()(templates);
15 22 renderer.addTemplates(templates);
16 }
17
18 623 return renderer;
19 }
20
21 function renderString(template, context, siteSettings, themeSettings, templates, hbVersion, requestParams) {
22 47 const renderer = buildRenderer(siteSettings, themeSettings, templates, hbVersion, requestParams);
23 47 return renderer.renderString(template, context);
24 }
25
26 function render(template, context, siteSettings, themeSettings, templates, hbVersion, requestParams) {
27 6 const renderer = buildRenderer(siteSettings, themeSettings, templates, hbVersion, requestParams);
28 6 return renderer.render(template, context);
29 }
30
31 // Return a function that is used to run through a set of test cases using renderString
32 function testRunner({context, siteSettings, themeSettings, templates, renderer, hbVersion, requestParams}) {
33 78 return (testCases, done) => {
34 const promises = testCases.map(testCase => {
35 const render = testCase.renderer ||
36 renderer ||
37 buildRenderer(testCase.siteSettings || siteSettings,
38 testCase.themeSettings || themeSettings,
39
testCase.templates
|| templates,
40
testCase.hbVersion
|| hbVersion,
41
testCase.requestParams
|| requestParams);
42
43 560 return render.renderString(testCase.input, testCase.context || context).then(result => {
44 expect(result).to.be.equal(testCase.output);
45 });
46 });
47
48 // Return a promise to run all the given test cases, then call done
49 252 return Promise.all(promises).then(() => { done(); });
50 }
51 }
52
53 // Return a random string
54 function randomString() {
55 5 return Crypto.randomBytes(10).toString('hex');
56 }
57
58 // Return a random integer
59 function randomInt(low, high) {
60
low =
low
|| 0;
61
high =
high
|| 1e9;
62 1 return Math.floor(Math.random() * (high - low) + low);
63 }
64
65 1 module.exports = {
66 buildRenderer,
67 renderString,
68 render,
69 testRunner,
70 randomString,
71 randomInt,
72 };
73

spec/helpers/all.js

100%
118
118
0
Line Lint Hits Source
1 1 const Lab = require('lab'),
2 lab = exports.lab = Lab.script(),
3 describe = lab.experiment,
4 it = lab.it,
5 testRunner = require('../spec-helpers').testRunner;
6
7 1 describe('all helper', function() {
8 const context = {
9 num1: 1,
10 num2: 2,
11 product: {a: 1, b: 2},
12 string: 'yolo',
13 alwaysTrue: true,
14 alwaysFalse: false,
15 emptyArray: [],
16 emptyObject: {},
17 itemArray: [1,2],
18 emptyString: "",
19 big: 'big',
20 alwaysNull: null,
21 };
22
23 // Build a test runner that uses a default context
24 1 const runTestCases = testRunner({context});
25
26 1 it('(with single argument) should behave like if helper', function(done) {
27 runTestCases([
28 {
29 input: '{{#all 1}}{{big}}{{/all}}',
30 output: 'big',
31 },
32 {
33 input: '{{#all 1}}big{{/all}}',
34 output: 'big',
35 },
36 {
37 input: '{{#all "x"}}big{{/all}}',
38 output: 'big',
39 },
40 {
41 input: '{{#all ""}}big{{/all}}',
42 output: '',
43 },
44 {
45 input: '{{#all 0}}big{{/all}}',
46 output: '',
47 },
48 {
49 input: '{{#all ""}}big{{else}}small{{/all}}',
50 output: 'small',
51 },
52 {
53 input: '{{#all 0}}big{{else}}small{{/all}}',
54 output: 'small',
55 },
56 {
57 input: '{{#all num2}}big{{else}}small{{/all}}',
58 output: 'big',
59 },
60 {
61 input: '{{#all product}}big{{else}}small{{/all}}',
62 output: 'big',
63 },
64 {
65 input: '{{#all itemArray}}big{{else}}small{{/all}}',
66 output: 'big',
67 },
68 {
69 input: '{{#all string}}big{{else}}small{{/all}}',
70 output: 'big',
71 },
72 {
73 input: '{{#all emptyObject}}big{{else}}small{{/all}}',
74 output: 'small',
75 },
76 ], done);
77 });
78
79 1 it('should render "big" if all conditions truthy', function(done) {
80 runTestCases([
81 {
82 input: '{{#all "1" true}}big{{/all}}',
83 output: 'big',
84 },
85 {
86 input: '{{#all 1 "1"}}big{{/all}}',
87 output: 'big',
88 },
89 {
90 input: '{{#all "text" alwaysTrue}}big{{/all}}',
91 output: 'big',
92 },
93 {
94 input: '{{#all alwaysTrue product num1 num2}}big{{/all}}',
95 output: 'big',
96 },
97 {
98 input: '{{#all alwaysTrue itemArray string}}big{{/all}}',
99 output: 'big',
100 },
101 ], done);
102 });
103
104 1 it('should render empty if any condition is falsy', function(done) {
105 runTestCases([
106 {
107 input: '{{#all emptyString num1}}big{{/all}}',
108 output: '',
109 },
110 {
111 input: '{{#all true 0 emptyArray alwaysTrue}}big{{/all}}',
112 output: '',
113 },
114 {
115 input: '{{#all true "" alwaysTrue}}big{{/all}}',
116 output: '',
117 },
118 {
119 input: '{{#all alwaysNull}}big{{/all}}',
120 output: '',
121 },
122 ], done);
123 });
124 });
125

spec/helpers/any.js

100%
133
133
0
Line Lint Hits Source
1 1 const Lab = require('lab'),
2 lab = exports.lab = Lab.script(),
3 describe = lab.experiment,
4 it = lab.it,
5 testRunner = require('../spec-helpers').testRunner;
6
7 1 describe('any helper (with option hash)', function() {
8 const context = {
9 big: 'big',
10 arrayWithObjs: [{a: 1},{b: 1},{a: 2}]
11 };
12
13 // Build a test runner that uses a default context
14 1 const runTestCases = testRunner({context});
15
16 1 it('should return "big" with matching predicate ', function(done) {
17 runTestCases([
18 {
19 input: '{{#any arrayWithObjs a=1}}{{big}}{{/any}}',
20 output: 'big',
21 },
22 {
23 input: '{{#any arrayWithObjs a=2}}{{big}}{{/any}}',
24 output: 'big',
25 },
26 {
27 input: '{{#any arrayWithObjs b=1}}{{big}}{{/any}}',
28 output: 'big',
29 },
30 ], done);
31 });
32
33 1 it('should return nothing without matching predicate ', function(done) {
34 runTestCases([
35 {
36 input: '{{#any arrayWithObjs b=2}}{{big}}{{/any}}',
37 output: '',
38 },
39 {
40 input: '{{#any arrayWithObjs c=1}}{{big}}{{/any}}',
41 output: '',
42 },
43 {
44 input: '{{#any arrayWithObjs num=2}}{{big}}{{/any}}',
45 output: '',
46 },
47 {
48 input: '{{#any foobar num=2}}{{big}}{{/any}}',
49 output: '',
50 context: {
51 foobar: null
52 }
53 },
54 {
55 input: '{{#any foobar num=2}}{{big}}{{/any}}',
56 output: '',
57 context: {
58 foobar: undefined
59 }
60 },
61 {
62 input: '{{#any undefined num=1}}{{big}}{{/any}}',
63 output: '',
64 context: {
65 foobar: undefined
66 }
67 },
68 {
69 input: '{{#any null num=null}}{{big}}{{/any}}',
70 output: '',
71 },
72 ], done);
73 });
74 });
75
76
77 1 describe('any helper (with multiple arguments)', function() {
78 // DEPRECATED: Moved to #or helper
79 const context = {
80 num1: 1,
81 num2: 2,
82 product: {a: 1, b: 2},
83 string: 'yolo',
84 alwaysTrue: true,
85 alwaysFalse: false,
86 emptyArray: [],
87 emptyObject: {},
88 itemArray: [1,2],
89 big: 'big',
90 arrayWithObjs: [{a: 1},{b: 1},{a: 2}]
91 };
92
93 // Build a test runner that uses a default context
94 1 const runTestCases = testRunner({context});
95
96 1 it('should return "big" if at least one arg valid', function(done) {
97 runTestCases([
98 {
99 input: '{{#any arrayWithObjs string}}{{big}}{{/any}}',
100 output: 'big',
101 },
102 {
103 input: '{{#any "something test" itemArray}}{{big}}{{/any}}',
104 output: 'big',
105 },
106 {
107 input: '{{#any "this is before the other test"}}{{big}}{{/any}}',
108 output: 'big',
109 },
110 {
111 input: '{{#any alwaysFalse emptyArray string}}{{big}}{{/any}}',
112 output: 'big',
113 },
114 ], done);
115 });
116
117 1 it('should return "" when no arguments are valid', function(done) {
118 runTestCases([
119 {
120 input: '{{#any emptyArray emptyObject alwaysFalse}}{{big}}{{/any}}',
121 output: '',
122 },
123 {
124 input: '{{#any "" false}}{{big}}{{/any}}',
125 output: '',
126 },
127 {
128 input: '{{#any fooBar}}{{big}}{{/any}}',
129 output: '',
130 context: {
131 fooBar: null
132 }
133 },
134 {
135 input: '{{#any fooBar}}{{big}}{{/any}}',
136 output: '',
137 context: {}
138 },
139 {
140 input: '{{#any undefined}}{{big}}{{/any}}',
141 output: '',
142 },
143 ], done);
144 });
145 });
146

spec/helpers/assignVar-getVar.js

100%
179
179
0
Line Lint Hits Source
1 1 const Lab = require('lab'),
2 lab = exports.lab = Lab.script(),
3 describe = lab.experiment,
4 it = lab.it,
5 specHelpers = require('../spec-helpers'),
6 testRunner = specHelpers.testRunner,
7 renderString = specHelpers.renderString;
8 1 const AssignVarHelper = require('../../helpers/assignVar')[0];
9
10 1 describe('assignVar and getVar helpers', function() {
11
12 const generateOverflowString = (length, char) => {
13 return `${[...Array(length).keys()].map(_=>char).join("")}`;
14 }
15
16 1 const context = {
17 value1: "Big",
18 value2: "Commerce",
19 value3: 12,
20 value4: -12.34,
21 valueUnderStringBuffer: generateOverflowString(AssignVarHelper.max_length-1, "U"),
22 // Bug in testing suite, strings in context that are over or equal to the max length are not being sent to the
23 // helpers. instead an `undefined` is being sent. To handle this we can use a template string in the tests
24 // directly to pretend that the string is being sent to the helpers.
25 valueFillStringBuffer: generateOverflowString(AssignVarHelper.max_length, "F"),
26 valueOverflowStringBufferBy1: generateOverflowString(AssignVarHelper.max_length+1, "O"),
27 valueEmptyString: "",
28 valueNull: null,
29 valueEncodedSafeString: "<script>alert('XSS')</script>",
30 };
31
32 1 const runTestCases = testRunner({context});
33
34 1 it('should throw an exception if the assignVar key is not a string', function (done) {
35 renderString('{{assignVar 1 2}}').catch(_ => {
36 done();
37 });
38 });
39
40 1 it('should throw an exception if the getVar key is not a string', function (done) {
41 renderString('{{getVar 2}}').catch(_ => {
42 done();
43 });
44 });
45
46 1 it('should assign and get variables', function(done) {
47 runTestCases([
48 {
49 // New test case: Assigning a variable with an empty string value
50 input: "{{assignVar 'data1' valueEmptyString}}{{getVar 'data1'}}",
51 output: '',
52 },
53 {
54 // New test case: Assigning a variable with a string value, then delete it by assigning null
55 input: "{{assignVar 'data1' value1}}{{getVar 'data1'}} {{assignVar 'data1' null}}{{getVar 'data1'}} {{assignVar 'data1' value1}}{{getVar 'data1'}} {{assignVar 'data1' valueNull}}{{getVar 'data1'}}",
56 output: 'Big Big ',
57 },
58 {
59 // New test case: Assigning a variable with a string value, then delete it by assigning undefined
60 input: "{{assignVar 'data1' value1}}{{getVar 'data1'}} {{assignVar 'data1' undefined}}{{getVar 'data1'}}",
61 output: 'Big ',
62 },
63 {
64 input: "{{assignVar 'data1' value1}}{{assignVar 'data2' 12}}{{getVar 'data1'}} {{getVar 'data2'}}",
65 output: 'Big 12',
66 },
67 {
68 input: "{{assignVar 'data1' value1}}{{assignVar 'data2' value4}}{{getVar 'data1'}} {{getVar 'data2'}}",
69 output: 'Big -12.34',
70 },
71 {
72 input: "{{assignVar 'data1' value1}}{{assignVar 'data2' value2}}{{getVar 'data1'}}{{getVar 'data2'}}",
73 output: 'BigCommerce',
74 },
75 {
76 input: "{{assignVar 'data1' null}}{{assignVar 'data2' undefined}}{{getVar 'data1'}}{{getVar 'data2'}}",
77 output: '',
78 },
79 ], done);
80 });
81
82 // Check to see if the assignVar still works when the situation is that no data has been stored yet, and a delete instruction is given.
83 1 it('should accept null and undefined as input before any variables are stored', function(done) {
84 runTestCases([
85 {
86 input: "{{assignVar 'data1' null}}{{assignVar 'data2' undefined}}{{getVar 'data1'}}{{getVar 'data2'}}",
87 output: '',
88 },
89 ], done);
90 });
91
92 1 it('should return empty string if variable is not defined', function(done) {
93 runTestCases([
94 {
95 input: "{{getVar 'data3'}}",
96 output: '',
97 },
98 {
99 input: "{{assignVar 'data1' value1}}{{getVar 'data3'}}",
100 output: '',
101 },
102 ], done);
103 });
104
105 1 it('should return empty string if variable is not *yet* defined', function(done) {
106 runTestCases([
107 {
108 input: "{{getVar 'data1'}}{{assignVar 'data1' value1}}",
109 output: '',
110 },
111 ], done);
112 });
113
114 1 it('should return integers with correct type', function(done) {
115 runTestCases([
116 {
117 input: "{{assignVar 'data1' value3}}{{assignVar 'data2' 10}}{{getVar 'data1'}} + {{getVar 'data2'}} = {{add (getVar 'data1') (getVar 'data2')}}",
118 output: '12 + 10 = 22',
119 },
120 ], done);
121 });
122
123 1 it('should accept a "SafeString" object as a valid alternative to a string (results should be a string when stored)', function(done) {
124 runTestCases([
125 {
126 input: "{{assignVar 'data1' (encodeHtmlEntities valueEncodedSafeString)}}{{getVar 'data1'}}",
127 output: '&amp;#x3C;script&amp;#x3E;alert(&amp;#x27;XSS&amp;#x27;)&amp;#x3C;/script&amp;#x3E;', // SAY NO TO XSS
128 },
129 ], done);
130 });
131
132 1 it(`should accept a string up to the maximum length of the buffer (Max length: ${AssignVarHelper.max_length})`, function(done) {
133 runTestCases([
134 {
135 input: "{{assignVar 'data1' valueUnderStringBuffer}}{{getVar 'data1'}}",
136 output: `${context.valueUnderStringBuffer}`,
137 },
138 ], done);
139 });
140
141 1 it(`should throw an error if buffer is filled (Max length: ${AssignVarHelper.max_length})`,
142 function(done) {
143 renderString(`{{assignVar "fill" "${context.valueFillStringBuffer}"}}`).catch(_ => {
144 // If inline injection of string is not done, the test will fail when it should pass via the catch.
145 // See context above for more information.
146 done();
147 });
148 }
149 );
150
151 1 it(`should throw an error if buffer is overflowed (Max length: ${AssignVarHelper.max_length})`,
152 function(done) {
153 renderString(`{{assignVar 'overflow' "${context.valueOverflowStringBufferBy1}"}}`).catch(_ => {
154 // If inline injection of string is not done, the test will fail when it should pass via the catch.
155 // See context above for more information.
156 done();
157 });
158 }
159 );
160
161 1 it(`should allow for the writing of up to and including maximum variables (Maximum: ${AssignVarHelper.max_keys})`, function(done) {
162 runTestCases([
163 {
164 input: `{{#for 1 ${AssignVarHelper.max_keys}}}{{assignVar (multiConcat 'data' this.$index) this.$index}}{{getVar (multiConcat 'data' this.$index)}}{{/for}}`,
165 output: [...Array(50).keys()]
166 .map(n=>n+1) // Start at 1
167 .join(""),
168 },
169 ], done);
170 });
171
172 1 it(`should fail when creating more than the maximum variables (Overflow at: ${AssignVarHelper.max_keys+1})`,
173 function(done) {
174 renderString(`{{#for 1 ${AssignVarHelper.max_keys+1}}}{{assignVar (multiConcat 'data' this.$index) this.$index}}{{getVar (multiConcat 'data' this.$index)}}{{/for}}`,
175 ).catch(_ => {
176 done();
177 });
178 }
179 );
180
181 1 it("should allow for more variable assignments when some are deleted", function(done) {
182 runTestCases([
183 {
184 input: `
185 {{~#for 1 ${AssignVarHelper.max_keys}}}{{assignVar (multiConcat 'data' this.$index) this.$index}}{{getVar (multiConcat 'data' this.$index)}}{{/for}}
186 {{~assignVar 'data1' null}}
187 {{~assignVar 'data2' null}}
188 {{~#for ${AssignVarHelper.max_keys+1} ${AssignVarHelper.max_keys+2}}}{{assignVar (multiConcat 'data' this.$index) this.$index}}{{getVar (multiConcat 'data' this.$index)}}{{/for}}
189 `.trim(),
190 output: [...Array(50).keys()]
191 .map(n=>n+1) // Start at 1
192 .join("") + "51" + "52",
193 },
194 ], done);
195 });
196
197 1 it('should return undefined accessing proto/constructor', function(done) {
198 runTestCases([
199 {
200 input: "{{getVar '__proto__'}}",
201 output: '',
202 },
203 {
204 input: "{{getVar 'constructor'}}",
205 output: '',
206 },
207 ], done);
208 });
209 });
210

spec/helpers/block.js

100%
80
80
0
Line Lint Hits Source
1 1 const Code = require('code');
2 1 const Lab = require('lab');
3 1 const lab = exports.lab = Lab.script();
4 1 const describe = lab.experiment;
5 1 const expect = Code.expect;
6 1 const it = lab.it;
7 1 const { render } = require('../spec-helpers');
8
9 1 describe('partial and block helpers', function () {
10 it('should insert partial into the corresponding block', function (done) {
11 var templates = {
12 template: '{{#partial "page"}}<h1>{{title}}</h1><p>{{content}}</p>{{/partial}}{{> layout}}',
13 layout: '<html><body>{{#block "page"}}{{/block}}</body>/<html>',
14 },
15 context = {
16 title: 'Hello',
17 content: 'World',
18 };
19
20 1 render('template', context, {}, {}, templates).then(result => {
21 expect(result).to.contain('<html><body><h1>Hello</h1><p>World</p></body>/<html>');
22 1 done();
23 });
24 });
25
26 1 it('should not trigger an error if partial name is empty, fallback case used instead', function (done) {
27 var templates = {
28 template: '{{#partial }}World{{/partial}}{{> layout}}',
29 layout: 'Hello{{#block "page"}} Brave New World{{/block}}',
30 };
31
32 1 render('template', {}, {}, {}, templates).then(result => {
33 expect(result).to.be.equal('Hello Brave New World');
34 1 done();
35 });
36 });
37
38 1 it('should not trigger an error if block name is empty', function (done) {
39 var templates = {
40 template: '{{#partial "page" "2134"}}World{{/partial}}{{> layout}}',
41 layout: 'Hello{{#block}}{{/block}}',
42 };
43
44 1 render('template', {}, {}, {}, templates).then(result => {
45 expect(result).to.be.equal('Hello');
46 1 done();
47 });
48 });
49
50 1 it('should successfully render template', function (done) {
51 const templateContent = "some-content";
52 1 const templates = {
53 "layout/base": templateContent,
54 template: `{{#JSONparse '{"layout/base":{}}'}}{{#partial this}}{{/partial}}{{/JSONparse}}{{>layout/base}}`,
55 };
56 1 render('template', {}, {}, {}, templates).then(result => {
57 expect(result).to.be.equal(templateContent);
58 1 done();
59 });
60 });
61
62 1 it('should successfully render template with context', function (done) {
63 const templateContent = "Hello, world!";
64 1 const templates = {
65 template: `{{#partial "base"}}Hello, world!{{/partial}}{{#partial notPartials}}{{/partial}}{{> base}}`,
66 };
67 1 const context = {
68 notPartials: {
69 "base": {}
70 }
71 }
72 1 render('template', context, {}, {}, templates).then(result => {
73 expect(result).to.be.equal(templateContent);
74 1 done();
75 });
76 });
77
78 1 it('should return empty string if using a reserved object property name', function (done) {
79 const templates = {
80 template: '{{#partial "__proto__"}}Page partial content.{{/partial}}{{> layout}}',
81 layout: '{{#block "constructor"}}{{/block}}',
82 };
83
84 1 const context = {};
85
86 1 render('template', context, {}, {}, templates).then(result => {
87 expect(result).to.equal('');
88 1 done();
89 });
90 });
91 });
92

spec/helpers/cdn.js

100%
323
323
0
Line Lint Hits Source
1 1 const Lab = require('lab'),
2 lab = exports.lab = Lab.script(),
3 describe = lab.experiment,
4 it = lab.it,
5 {testRunner, buildRenderer} = require('../spec-helpers');
6 1 const {expect} = require("code");
7
8 1 describe('cdn helper', function () {
9 const context = {
10 brand: 'visa',
11 };
12 1 const siteSettings = {
13 cdn_url: 'https://cdn.bcapp/3dsf74g',
14 theme_version_id: '123',
15 };
16 1 const themeSettings = {
17 cdn: {
18 customcdn1: 'https://bigcommerce.customcdn.net',
19 customcdn2: 'http://cdn.mystore.com',
20 }
21 };
22 1 const hbVersion = 'v4';
23
24 // Build a test runner that uses a default context
25 1 const runTestCases = testRunner({context, siteSettings, themeSettings, hbVersion});
26
27 1 it('should render the css cdn url and produce resource hints when attribute resourceHint is part of the template', function (done) {
28 const renderer = buildRenderer(siteSettings, themeSettings, {}, hbVersion);
29 1 const runTestCases = testRunner({context, renderer});
30 1 runTestCases([
31 {
32 input: '{{cdn "assets/css/style.css" resourceHint="preload" as="style" crossorigin="use-credentials"}}',
33 output: 'https://cdn.bcapp/3dsf74g/stencil/123/css/style.css',
34 },
35 {
36 input: '{{cdn "/assets/css/style.modal.css" resourceHint="preconnect" as="style" crossorigin="anonymous"}}',
37 output: 'https://cdn.bcapp/3dsf74g/stencil/123/css/style.modal.css',
38 },
39 {
40 input: '{{cdn "/assets/css/style.modal.css" as="style" crossorigin="use-credentials"}}',
41 output: 'https://cdn.bcapp/3dsf74g/stencil/123/css/style.modal.css',
42 }
43 ], () => {
44 const hints = renderer.getResourceHints();
45 1 expect(hints).to.have.length(2);
46 1 hints.forEach(hint => {
47 expect(hint.state).to.satisfy((state) => state === 'preload' || state === 'preconnect');
48 2 expect(hint.type).to.equals("style");
49 2 expect(hint.cors).to.satisfy((cors) => cors === 'anonymous' || cors === 'use-credentials');
50 });
51 1 done();
52 });
53 });
54
55 1 it('should render the css cdn url and produce resource hint without as/type attribute', function (done) {
56 const renderer = buildRenderer(siteSettings, themeSettings, {}, hbVersion);
57 1 const runTestCases = testRunner({context, renderer});
58 1 runTestCases([
59 {
60 input: '{{cdn "assets/css/style.css" resourceHint="preload" as="should-not-be-included"}}',
61 output: 'https://cdn.bcapp/3dsf74g/stencil/123/css/style.css',
62 }
63 ], () => {
64 const hints = renderer.getResourceHints();
65 1 expect(hints).to.have.length(1);
66 1 hints.forEach(hint => {
67 expect(hint.state).to.equals('preload');
68 1 expect(hint.type).to.not.exist();
69 1 expect(hint.cors).to.equals('no');
70 });
71 1 done();
72 });
73 });
74
75 1 it('should render the css cdn url', function (done) {
76 runTestCases([
77 {
78 input: '{{cdn "assets/css/style.css"}}',
79 output: 'https://cdn.bcapp/3dsf74g/stencil/123/css/style.css',
80 },
81 {
82 input: '{{cdn "/assets/css/style.css"}}',
83 output: 'https://cdn.bcapp/3dsf74g/stencil/123/css/style.css',
84 },
85 ], done);
86 });
87
88 1 it('should render normal assets cdn url', function (done) {
89 runTestCases([
90 {
91 input: '{{cdn "assets/js/app.js"}}',
92 output: 'https://cdn.bcapp/3dsf74g/stencil/123/js/app.js',
93 },
94 {
95 input: '{{cdn "assets/img/image.jpg"}}',
96 output: 'https://cdn.bcapp/3dsf74g/stencil/123/img/image.jpg',
97 },
98 ], done);
99 });
100
101 1 it('should not use the cdn url', function (done) {
102 runTestCases([
103 {
104 input: '{{cdn "assets/img/image.jpg"}}',
105 output: '/assets/img/image.jpg',
106 siteSettings: {},
107 themeSettings: {},
108 },
109 {
110 input: '{{cdn "assets/img/image.jpg"}}',
111 output: '/assets/img/image.jpg',
112 siteSettings: {},
113 themeSettings: {},
114 },
115 ], done);
116 });
117
118 1 it('should return the same value if it is a full url', function (done) {
119 runTestCases([
120 {
121 input: '{{cdn "https://example.com/app.js"}}',
122 output: 'https://example.com/app.js',
123 },
124 {
125 input: '{{cdn "http://example.com/app.js"}}',
126 output: 'http://example.com/app.js',
127 },
128 {
129 input: '{{cdn "//example.com/app.js"}}',
130 output: '//example.com/app.js',
131 },
132 ], done);
133 });
134
135 1 it('should delete any . and / from the begining of the path if this is not a full path', function (done) {
136 runTestCases([
137 {
138 input: '{{cdn "../img/icon-sprite.svg"}}',
139 output: 'https://cdn.bcapp/3dsf74g/stencil/123/img/icon-sprite.svg',
140 },
141 {
142 input: '{{cdn "./img/icon-sprite.svg"}}',
143 output: 'https://cdn.bcapp/3dsf74g/stencil/123/img/icon-sprite.svg',
144 },
145 {
146 input: '{{cdn "/img/icon-sprite.svg"}}',
147 output: 'https://cdn.bcapp/3dsf74g/stencil/123/img/icon-sprite.svg',
148 },
149 {
150 input: '{{cdn "../../img/icon-sprite.svg"}}',
151 output: 'https://cdn.bcapp/3dsf74g/stencil/123/img/icon-sprite.svg',
152 },
153 {
154 input: '{{cdn "../../../img/icon-sprite.svg"}}',
155 output: 'https://cdn.bcapp/3dsf74g/stencil/123/img/icon-sprite.svg',
156 },
157 {
158 input: '{{cdn "../../../IMG/icon-sprite.svg"}}',
159 output: 'https://cdn.bcapp/3dsf74g/stencil/123/IMG/icon-sprite.svg',
160 },
161 {
162 input: '{{cdn "../../../456IMG/icon-sprite.svg"}}',
163 output: 'https://cdn.bcapp/3dsf74g/stencil/123/456IMG/icon-sprite.svg',
164 },
165 {
166 input: '{{cdn "../../../456IMG/icon-sprite.svg/"}}',
167 output: 'https://cdn.bcapp/3dsf74g/stencil/123/456IMG/icon-sprite.svg/',
168 },
169 {
170 input: '{{cdn "../../../456IMG/icon-sprite.svg."}}',
171 output: 'https://cdn.bcapp/3dsf74g/stencil/123/456IMG/icon-sprite.svg.',
172 },
173 {
174 input: '{{cdn "../../../456IMG/icon-sprite.svg./"}}',
175 output: 'https://cdn.bcapp/3dsf74g/stencil/123/456IMG/icon-sprite.svg./',
176 },
177 {
178 input: '{{cdn "../../../456IMG/icon-sprite.svg../"}}',
179 output: 'https://cdn.bcapp/3dsf74g/stencil/123/456IMG/icon-sprite.svg../',
180 },
181 {
182 input: '{{cdn "../../../456IMG/icon-sprite.svg../../"}}',
183 output: 'https://cdn.bcapp/3dsf74g/stencil/123/456IMG/icon-sprite.svg../../',
184 },
185 ], done);
186 });
187
188 1 it('should return an empty string if no path is provided', function (done) {
189 runTestCases([
190 {
191 input: '{{cdn ""}}',
192 output: '',
193 },
194 ], done);
195 });
196
197 1 it('should return a webDav asset if webdav protocol specified', function (done) {
198 runTestCases([
199 {
200 input: '{{cdn "webdav:img/image.jpg"}}',
201 output: 'https://cdn.bcapp/3dsf74g/content/img/image.jpg',
202 },
203 {
204 input: '{{cdn "webdav:/img/image.jpg"}}',
205 output: 'https://cdn.bcapp/3dsf74g/content/img/image.jpg',
206 },
207 {
208 input: '{{cdn "webdav://img/image.jpg"}}',
209 output: 'https://cdn.bcapp/3dsf74g/content/img/image.jpg',
210 },
211 ], done);
212 });
213
214 1 it('should not return a webDav asset if webdav protocol is not correct', function (done) {
215 runTestCases([
216 {
217 input: '{{cdn "webbav:img/image.jpg"}}',
218 output: '/img/image.jpg',
219 },
220 {
221 input: '{{cdn "webbav:/img/image.jpg"}}',
222 output: '/img/image.jpg',
223 },
224 ], done);
225 });
226
227 1 it('should return a custom CDN asset if protocol is configured', function (done) {
228 runTestCases([
229 {
230 input: '{{cdn "customcdn1:img/image.jpg"}}',
231 output: 'https://bigcommerce.customcdn.net/img/image.jpg',
232 },
233 {
234 input: '{{cdn "customcdn1:/img/image.jpg"}}',
235 output: 'https://bigcommerce.customcdn.net/img/image.jpg',
236 },
237 {
238 input: '{{cdn "customcdn2:img/image.jpg"}}',
239 output: 'http://cdn.mystore.com/img/image.jpg',
240 },
241 {
242 input: '{{cdn "customcdn2:/img/image.jpg"}}',
243 output: 'http://cdn.mystore.com/img/image.jpg',
244 },
245 ], done);
246 });
247
248 1 it('should return a custom CDN asset when using nested helper', function (done) {
249 runTestCases([
250 {
251 input: '{{cdn (concat "customcdn1:" "img/image.jpg")}}',
252 output: 'https://bigcommerce.customcdn.net/img/image.jpg',
253 },
254 {
255 input: '{{cdn (concat "customcdn1:" "/img/image.jpg")}}',
256 output: 'https://bigcommerce.customcdn.net/img/image.jpg',
257 },
258 {
259 input: '{{cdn (concat "customcdn2:" "img/image.jpg")}}',
260 output: 'http://cdn.mystore.com/img/image.jpg',
261 },
262 {
263 input: '{{cdn (concat "customcdn2:" "/img/image.jpg")}}',
264 output: 'http://cdn.mystore.com/img/image.jpg',
265 },
266 ], done);
267 });
268
269 1 it('should return a custom CDN asset when using nested concat helper', function (done) {
270 runTestCases([
271 {
272 input: '{{cdn (concat (concat "customcdn2:" "/img/image.jpg") "?test")}}',
273 output: 'http://cdn.mystore.com/img/image.jpg?test',
274 },
275 ], done);
276 });
277
278 1 it('should return a local CDN asset if no cdn url is configured', function (done) {
279 runTestCases([
280 {
281 input: '{{cdn "customcdn1:img/image.jpg"}}',
282 output: '/assets/cdn/customcdn1/img/image.jpg',
283 siteSettings: {},
284 },
285 {
286 input: '{{cdn "customcdn1:/img/image.jpg"}}',
287 output: '/assets/cdn/customcdn1/img/image.jpg',
288 siteSettings: {},
289 },
290 {
291 input: '{{cdn "customcdn2:img/image.jpg"}}',
292 output: '/assets/cdn/customcdn2/img/image.jpg',
293 siteSettings: {},
294 },
295 {
296 input: '{{cdn "customcdn2:/img/image.jpg"}}',
297 output: '/assets/cdn/customcdn2/img/image.jpg',
298 siteSettings: {},
299 },
300 {
301 input: "{{cdn (concat (concat 'img/payment-methods/' brand) '.svg')}}",
302 output: 'https://cdn.bcapp/3dsf74g/stencil/123/img/payment-methods/visa.svg',
303 }
304 ], done);
305 });
306
307 1 it('should not return a custom CDN asset if protocol is not configured', function (done) {
308 runTestCases([
309 {
310 input: '{{cdn "customcdn1:img/image.jpg"}}',
311 output: '/img/image.jpg',
312 themeSettings: {},
313 },
314 {
315 input: '{{cdn "customcdn1:/img/image.jpg"}}',
316 output: '/img/image.jpg',
317 themeSettings: {},
318 },
319 ], done);
320 });
321
322 1 it('should return basic asset URL if protocol is not correct', function (done) {
323 runTestCases([
324 {
325 input: '{{cdn "randomProtocol::img/image.jpg"}}',
326 output: '/img/image.jpg',
327 },
328 {
329 input: '{{cdn "randomProtocol:/img/image.jpg"}}',
330 output: '/img/image.jpg',
331 },
332 ], done);
333 });
334
335 1 it('should avoid double slash in path', function (done) {
336 runTestCases([
337 {
338 input: '{{cdn "img/icon-sprite.svg"}}',
339 output: 'https://cdn.bcapp/3dsf74g/stencil/123/img/icon-sprite.svg',
340 }
341 ], done);
342 });
343 });
344

spec/helpers/compare.js

100%
102
102
0
Line Lint Hits Source
1 1 const Lab = require('lab'),
2 lab = exports.lab = Lab.script(),
3 describe = lab.experiment,
4 it = lab.it,
5 testRunner = require('../spec-helpers').testRunner;
6
7 1 describe('compare helper', function() {
8 const context = {
9 num1: 1,
10 num2: 2,
11 product: {a: 1, b: 2},
12 string: 'yolo'
13 };
14
15 // Build a test runner that uses a default context
16 1 const runTestCases = testRunner({context});
17
18 1 it('should render "big" if all compares match', function(done) {
19 runTestCases([
20 {
21 input: '{{#compare "1" num1 operator="=="}}big{{/compare}}',
22 output: 'big',
23 },
24 {
25 input: '{{#compare 1 num1 operator="==="}}big{{/compare}}',
26 output: 'big',
27 },
28 {
29 input: '{{#compare 2 num1 operator="!=="}}big{{/compare}}',
30 output: 'big',
31 },
32 {
33 input: '{{#compare num2 num1 operator="!="}}big{{/compare}}',
34 output: 'big',
35 },
36 {
37 input: '{{#compare num2 num1 operator=">"}}big{{/compare}}',
38 output: 'big',
39 },
40 {
41 input: '{{#compare num1 num2 operator="<"}}big{{/compare}}',
42 output: 'big',
43 },
44 {
45 input: '{{#compare num2 num1 operator=">="}}big{{/compare}}',
46 output: 'big',
47 },
48 {
49 input: '{{#compare num1 num2 operator="<="}}big{{/compare}}',
50 output: 'big',
51 },
52 {
53 input: '{{#compare product "object" operator="typeof"}}big{{/compare}}',
54 output: 'big',
55 },
56 {
57 input: '{{#compare string "string" operator="typeof"}}big{{/compare}}',
58 output: 'big',
59 },
60 ], done);
61 });
62
63 1 it('should render empty for all cases', function(done) {
64 runTestCases([
65 {
66 input: '{{#compare "2" num1 operator="=="}}big{{/compare}}',
67 output: '',
68 },
69 {
70 input: '{{#compare 2 num1 operator="==="}}big{{/compare}}',
71 output: '',
72 },
73 {
74 input: '{{#compare 1 num1 operator="!=="}}big{{/compare}}',
75 output: '',
76 },
77 {
78 input: '{{#compare num2 2 operator="!="}}big{{/compare}}',
79 output: '',
80 },
81 {
82 input: '{{#compare num1 20 operator=">"}}big{{/compare}}',
83 output: '',
84 },
85 {
86 input: '{{#compare 4 num2 operator="<"}}big{{/compare}}',
87 output: '',
88 },
89 {
90 input: '{{#compare num1 40 operator=">="}}big{{/compare}}',
91 output: '',
92 },
93 {
94 input: '{{#compare num2 num1 operator="<="}}big{{/compare}}',
95 output: '',
96 },
97 {
98 input: '{{#compare product "string" operator="typeof"}}big{{/compare}}',
99 output: '',
100 },
101 {
102 input: '{{#compare string "object" operator="typeof"}}big{{/compare}}',
103 output: '',
104 },
105 ], done);
106 });
107 });
108

spec/helpers/concat.js

100%
20
20
0
Line Lint Hits Source
1 1 const Lab = require('lab'),
2 lab = exports.lab = Lab.script(),
3 describe = lab.experiment,
4 it = lab.it,
5 testRunner = require('../spec-helpers').testRunner;
6
7 1 describe('concat helper', function() {
8 const context = {
9 var1: 'hello',
10 var2: 'world',
11 };
12
13 // Build a test runner
14 1 const runTestCases = testRunner({context});
15
16 1 it('should concatanate two strings', function(done) {
17 runTestCases([
18 {
19 input: '{{concat var1 var2}}',
20 output: 'helloworld',
21 },
22 ], done);
23 });
24 });
25
26

spec/helpers/decrementVar.js

100%
62
62
0
Line Lint Hits Source
1 1 const Lab = require('lab'),
2 lab = exports.lab = Lab.script(),
3 describe = lab.experiment,
4 it = lab.it,
5 specHelpers = require('../spec-helpers'),
6 testRunner = specHelpers.testRunner,
7 renderString = specHelpers.renderString;
8
9
10 1 describe('decrementVar helper', function() {
11 const context = {
12 value1: 12
13 };
14
15 1 const runTestCases = testRunner({context});
16
17 1 it('should throw an exception if the decrementVar key is not a string', function (done) {
18 renderString('{{decrementVar 1}}').catch(e => {
19 done();
20 });
21 });
22
23 1 it('should correctly decrement', function(done) {
24 runTestCases([
25 {
26 input: "{{decrementVar 'data1'}}{{getVar 'data1'}} {{decrementVar 'data1'}}{{getVar 'data1'}} {{decrementVar 'data1'}}{{getVar 'data1'}}",
27 output: '00 -1-1 -2-2',
28 },
29 ], done);
30 });
31
32 1 it('should correctly decrement an existing variable', function(done) {
33 runTestCases([
34 {
35 input: "{{assignVar 'data1' value1}}{{getVar 'data1'}} {{decrementVar 'data1'}}",
36 output: '12 11',
37 },
38 {
39 input: "{{assignVar 'data1' 12}}{{getVar 'data1'}} {{decrementVar 'data1'}}",
40 output: '12 11',
41 },
42 {
43 input: "{{assignVar 'data1' -12}}{{getVar 'data1'}} {{decrementVar 'data1'}}",
44 output: '-12 -13',
45 },
46 ], done);
47 });
48
49 1 it('should correctly overwrite an existing non-integer variable', function(done) {
50 runTestCases([
51 {
52 input: "{{assignVar 'data1' 'a'}}{{getVar 'data1'}} {{decrementVar 'data1'}}",
53 output: 'a 0',
54 },
55 ], done);
56 });
57
58 1 it('should correctly return data accession object proto/constructor', function(done) {
59 runTestCases([
60 {
61 input: "{{decrementVar '__proto__'}}",
62 output: '0',
63 },
64 {
65 input: "{{decrementVar 'constructor'}}",
66 output: '0',
67 },
68 ], done);
69 });
70 });
71

spec/helpers/dynamicComponent.js

100%
42
42
0
Line Lint Hits Source
1 1 const path = require('path');
2 1 const Lab = require('lab'),
3 lab = exports.lab = Lab.script(),
4 describe = lab.experiment,
5 it = lab.it,
6 testRunner = require('../spec-helpers').testRunner;
7
8 1 describe('dynamicComponent helper', function() {
9 const templates = {
10 [path.normalize('component/fields/one')]: '1{{animal}}',
11 [path.normalize('component/fields/two')]: '2{{animal}}',
12 [path.normalize('component/fields/three')]: '3{{animal}}',
13 },
14 context = {
15 fields: [
16 {partial: 'one', animal: 'dog'},
17 {partial: 'two', animal: 'cat'},
18 {partial: 'three', animal: 'mouse'},
19 ],
20 field: {partial: 'two', animal: 'elephant'}
21 };
22
23 // Build a test runner
24 1 const runTestCases = testRunner({context, templates});
25
26 1 it('should render all dynamic templates accordingly', function(done) {
27 runTestCases([
28 {
29 input: '{{#each fields}}{{dynamicComponent "component/fields"}} {{/each}}',
30 output: '1dog 2cat 3mouse ',
31 },
32 ], done);
33 });
34
35 1 it('should return undefined when accessing proto/constructor', function(done) {
36 runTestCases([
37 {
38 input: '{{#each fields}}{{dynamicComponent "__proto__"}}{{/each}}',
39 output: '',
40 },
41 {
42 input: '{{#each fields}}{{dynamicComponent "constructor"}}{{/each}}',
43 output: '',
44 },
45 ], done);
46 });
47 });
48
49

spec/helpers/earlyHint.js

100%
65
65
0
Line Lint Hits Source
1 1 const Lab = require('lab'),
2 lab = exports.lab = Lab.script(),
3 describe = lab.experiment,
4 it = lab.it,
5 beforeEach = lab.beforeEach;
6 1 const {buildRenderer, testRunner} = require("../spec-helpers");
7 1 const {expect} = require("code");
8 1 const {
9 resourceHintAllowedTypes,
10 resourceHintAllowedStates,
11 resourceHintAllowedCors
12 } = require("../../helpers/lib/resourceHints");
13
14 function template(href, rel, type, cors) {
15 2 cors = cors ? `crossorigin="${cors}"` : '';
16 2 type = type ? `as="${type}"` : '';
17 2 return `{{ earlyHint "${href}" "${rel}" ${type} ${cors} }}`;
18 }
19
20 function randommer(items) {
21 3 return () => items[Math.floor(Math.random() * items.length)];
22 }
23
24 1 const randomHintState = randommer(Object.values(resourceHintAllowedStates));
25 1 const randomHintType = randommer(Object.values(resourceHintAllowedTypes));
26 1 const randomCors = randommer(Object.values(resourceHintAllowedCors));
27
28 1 let renderer, runTests;
29
30 1 describe('earlyHint', () => {
31
32 beforeEach(done => {
33 renderer = buildRenderer();
34 2 runTests = testRunner({renderer});
35 2 done();
36 });
37
38 1 it('should create a resource hint with only required properties', done => {
39 const path = '/asset/theme.css'
40 1 const state = randomHintState();
41 1 const input = template(path, state);
42 1 runTests([
43 {
44 input,
45 output: path
46 }
47 ], () => {
48 const hints = renderer.getResourceHints();
49 1 expect(hints).to.have.length(1);
50 1 expect(hints[0]).to.equals({src: path, state, cors: 'no'});
51 1 done();
52 });
53 });
54
55 1 it('should create a resource hint with all the properties', done => {
56 const path = '/asset/theme.css'
57 1 const state = randomHintState();
58 1 const type = randomHintType();
59 1 const cors = randomCors();
60 1 const input = template(path, state, type, cors);
61 1 runTests([
62 {
63 input,
64 output: path
65 }
66 ], () => {
67 const hints = renderer.getResourceHints();
68 1 expect(hints).to.have.length(1);
69 1 expect(hints[0]).to.equals({src: path, state, type, cors});
70 1 done();
71 });
72 });
73
74 });
75

spec/helpers/encodeHtmlEntities.js

100%
98
98
0
Line Lint Hits Source
1 1 const Lab = require('lab'),
2 lab = exports.lab = Lab.script(),
3 describe = lab.experiment,
4 it = lab.it,
5 specHelpers = require('../spec-helpers'),
6 testRunner = specHelpers.testRunner,
7 renderString = specHelpers.renderString;
8
9
10 1 describe('encodeHtmlEntities helper', function() {
11 const context = {
12 string: 'foo © bar ≠ baz 𝌆 qux',
13 quotes: '"some" \'quotes\'',
14 };
15
16 1 const runTestCases = testRunner({context});
17
18 // Some test cases lifted from https://github.com/mathiasbynens/he
19
20 1 it('should return a string with HTML entities encoded', function(done) {
21 runTestCases([
22 {
23 input: '{{encodeHtmlEntities "foo © bar ≠ baz 𝌆 qux"}}',
24 output: `foo &#xA9; bar &#x2260; baz &#x1D306; qux`,
25 },
26 {
27 input: '{{encodeHtmlEntities string}}',
28 output: `foo &#xA9; bar &#x2260; baz &#x1D306; qux`,
29 },
30 {
31 input: '{{encodeHtmlEntities "string"}}',
32 output: `string`,
33 },
34 {
35 input: '{{encodeHtmlEntities quotes}}',
36 output: `&#x22;some&#x22; &#x27;quotes&#x27;`,
37 },
38 {
39 input: '{{encodeHtmlEntities "an ampersand: &"}}',
40 output: `an ampersand: &#x26;`,
41 },
42 ], done);
43 });
44
45 1 it('should return a string with HTML entities encoded with named references', function(done) {
46 runTestCases([
47 {
48 input: '{{encodeHtmlEntities "foo © bar ≠ baz 𝌆 qux" useNamedReferences="true"}}',
49 output: `foo &copy; bar &ne; baz &#x1D306; qux`,
50 },
51 ], done);
52 });
53
54 1 it('should return a string with HTML entities encoded with decimal option', function(done) {
55 runTestCases([
56 {
57 input: '{{encodeHtmlEntities "foo © bar ≠ baz 𝌆 qux" decimal="true"}}',
58 output: `foo &#169; bar &#8800; baz &#119558; qux`,
59 },
60 ], done);
61 });
62
63 1 it('should return a string with HTML entities encoded with named references and decimal option', function(done) {
64 runTestCases([
65 {
66 input: '{{encodeHtmlEntities "foo © bar ≠ baz 𝌆 qux" useNamedReferences="true" decimal="true"}}',
67 output: `foo &copy; bar &ne; baz &#119558; qux`,
68 },
69 ], done);
70 });
71
72 1 it('should return a string with HTML entities encoded with encodeEverything option', function(done) {
73 runTestCases([
74 {
75 input: '{{encodeHtmlEntities "foo © bar ≠ baz 𝌆 qux" encodeEverything="true"}}',
76 output: `&#x66;&#x6F;&#x6F;&#x20;&#xA9;&#x20;&#x62;&#x61;&#x72;&#x20;&#x2260;&#x20;&#x62;&#x61;&#x7A;&#x20;&#x1D306;&#x20;&#x71;&#x75;&#x78;`,
77 },
78 ], done);
79 });
80
81 1 it('should return a string with HTML entities encoded with encodeEverything and useNamedReferences option', function(done) {
82 runTestCases([
83 {
84 input: '{{encodeHtmlEntities "foo © bar ≠ baz 𝌆 qux" encodeEverything="true" useNamedReferences="true"}}',
85 output: `&#x66;&#x6F;&#x6F;&#x20;&copy;&#x20;&#x62;&#x61;&#x72;&#x20;&ne;&#x20;&#x62;&#x61;&#x7A;&#x20;&#x1D306;&#x20;&#x71;&#x75;&#x78;`,
86 },
87 ], done);
88 });
89
90 1 it('should return a string with HTML entities encoded with allowUnsafeSymbols option', function(done) {
91 runTestCases([
92 {
93 input: '{{encodeHtmlEntities "foo © and & ampersand" allowUnsafeSymbols="true"}}',
94 output: `foo &#xA9; and & ampersand`,
95 },
96 ], done);
97 });
98
99 1 it('should throw an exception if an invalid named argument is passed', function (done) {
100 renderString('{{encodeHtmlEntities "foo © bar ≠ baz 𝌆 qux" useNamedReferences="blah"}}').catch(e => {
101 renderString('{{encodeHtmlEntities "foo © bar ≠ baz 𝌆 qux" blah="true"}}').catch(e => {
102 done();
103 });
104 });
105 });
106
107 1 it('should throw an exception if a non-string argument is passed', function (done) {
108 renderString('{{encodeHtmlEntities 123}}').catch(e => {
109 done();
110 });
111 });
112 });
113

spec/helpers/equals.js

100%
35
35
0
Line Lint Hits Source
1 1 const Lab = require('lab'),
2 lab = exports.lab = Lab.script(),
3 describe = lab.experiment,
4 it = lab.it,
5 testRunner = require('../spec-helpers').testRunner;
6
7 1 describe('equals helper', function() {
8 const context = {
9 value: 5
10 };
11
12 // Build a test runner
13 1 const runTestCases = testRunner({context});
14
15 1 it('should render yes if the value is equal to 5', function(done) {
16 runTestCases([
17 {
18 input: '{{#equals 5 value}}yes{{/equals}}',
19 output: 'yes',
20 },
21 {
22 input: '{{#equals value 5}}yes{{/equals}}',
23 output: 'yes',
24 },
25 ], done);
26 });
27
28 1 it('should render empty string', function(done) {
29 runTestCases([
30 {
31 input: '{{#equals 6 value}}yes{{/equals}}',
32 output: '',
33 },
34 {
35 input: '{{#equals value 6}}yes{{/equals}}',
36 output: '',
37 },
38 ], done);
39 });
40 });
41

spec/helpers/for.js

100%
120
120
0
Line Lint Hits Source
1 1 const Lab = require('lab'),
2 lab = exports.lab = Lab.script(),
3 describe = lab.experiment,
4 it = lab.it,
5 testRunner = require('../spec-helpers').testRunner;
6
7 1 describe('for helper', function() {
8 const context = { name: 'Joe' };
9
10 // Build a test runner
11 1 const runTestCases = testRunner({context});
12
13 1 it('should iterate once.', function(done) {
14 runTestCases([
15 {
16 input: '{{#for 1 this}}{{$index}}:{{name}} {{/for}}',
17 output: '1:Joe ',
18 },
19 {
20 input: '{{#for 1 1 this}}{{$index}}:{{name}} {{/for}}',
21 output: '1:Joe ',
22 },
23 {
24 input: '{{#for 0 0 this}}{{$index}}:{{name}} {{/for}}',
25 output: '0:Joe ',
26 },
27 {
28 input: '{{#for 1000 1000 this}}{{$index}}:{{name}} {{/for}}',
29 output: '1000:Joe ',
30 },
31 ], done);
32 });
33
34 1 it('should iterate 10 times', function(done) {
35 runTestCases([
36 {
37 input: '{{#for 10 this}}{{$index}}:{{name}} {{/for}}',
38 output: '1:Joe 2:Joe 3:Joe 4:Joe 5:Joe 6:Joe 7:Joe 8:Joe 9:Joe 10:Joe ',
39 },
40 {
41 input: '{{#for 1 10 this}}{{$index}}:{{name}} {{/for}}',
42 output: '1:Joe 2:Joe 3:Joe 4:Joe 5:Joe 6:Joe 7:Joe 8:Joe 9:Joe 10:Joe ',
43 },
44 {
45 input: '{{#for 0 9 this}}{{$index}}:{{name}} {{/for}}',
46 output: '0:Joe 1:Joe 2:Joe 3:Joe 4:Joe 5:Joe 6:Joe 7:Joe 8:Joe 9:Joe ',
47 },
48 {
49 input: '{{#for 1000 1010 this}}{{$index}}:{{name}} {{/for}}',
50 output: '1000:Joe 1001:Joe 1002:Joe 1003:Joe 1004:Joe 1005:Joe 1006:Joe 1007:Joe 1008:Joe 1009:Joe 1010:Joe ',
51 },
52 ], done);
53 });
54
55 1 it('should not iterate more than 100 times', function(done) {
56 const expected = '.'.repeat(100);
57 1 runTestCases([
58 {
59 input: '{{#for 0 99}}.{{/for}}',
60 output: expected,
61 },
62 {
63 input: '{{#for 1 100}}.{{/for}}',
64 output: expected,
65 },
66 {
67 input: '{{#for 0 3000 this}}.{{/for}}',
68 output: expected,
69 },
70 {
71 input: '{{#for 2015 3000}}.{{/for}}',
72 output: expected,
73 },
74 ], done);
75 });
76
77 1 it('should render w/o context', function(done) {
78 runTestCases([
79 {
80 input: '{{#for 10}}{{$index}} {{/for}}',
81 output: '1 2 3 4 5 6 7 8 9 10 ',
82 },
83 {
84 input: '{{#for 1 10}}{{$index}} {{/for}}',
85 output: '1 2 3 4 5 6 7 8 9 10 ',
86 },
87 {
88 input: '{{#for 0 9}}{{$index}} {{/for}}',
89 output: '0 1 2 3 4 5 6 7 8 9 ',
90 },
91 {
92 input: '{{#for 0 20}}.{{/for}}',
93 output: '.....................',
94 },
95 {
96 input: '{{#for 0 99}}{{/for}}',
97 output: '',
98 },
99 ], done);
100 });
101
102 1 it('should convert strings to integers and iterate 10 times', function(done) {
103 const context = {
104 start: '1',
105 end: '10'
106 };
107
108 1 runTestCases([
109 {
110 input: '{{#for start end}}{{$index}} {{/for}}',
111 output: '1 2 3 4 5 6 7 8 9 10 ',
112 context: context,
113 },
114 ], done);
115 });
116
117 1 it('should not iterate if "from" is less than "to"', function(done) {
118 const context = {
119 start: 10,
120 end: 1
121 };
122
123 1 runTestCases([
124 {
125 input: '{{#for start end}}{{$index}} {{/for}}',
126 output: '',
127 context: context,
128 },
129 ], done);
130 });
131 });
132

spec/helpers/get.js

100%
95
95
0
Line Lint Hits Source
1 1 const Lab = require('lab'),
2 lab = exports.lab = Lab.script(),
3 describe = lab.experiment,
4 it = lab.it,
5 testRunner = require('../spec-helpers').testRunner;
6
7 1 describe('get helper', function () {
8 const context = {
9 array: [1, 2, 3, 4, 5],
10 options: { a: { b: { c: 'd' } } },
11 aa: 'a',
12 ab: 'b',
13 };
14
15 1 const runTestCases = testRunner({ context });
16
17 1 it('gets a nested prop', (done) => {
18 runTestCases([
19 {
20 input: `{{get "a.b.c" this.options}}`,
21 output: `d`,
22 }
23 ], done);
24 });
25
26 1 it('does not access prototype properties', (done) => {
27 context.__proto__ = {x: 'yz'};
28 1 runTestCases([
29 {
30 input: `{{get "x" this}}`,
31 output: ``,
32 }
33 ], done);
34 });
35
36 1 it('accepts SafeString paths', (done) => {
37 runTestCases([
38 {
39 input: `{{get (concat 'a' 'a') this}}`,
40 output: `a`,
41 },
42 {
43 input: `{{get (concat 'a' 'b') this}}`,
44 output: `b`,
45 },
46 {
47 input: `{{get (concat 'options.a' '.b.c') this}}`,
48 output: `d`,
49 }
50 ], done);
51 });
52
53 1 it('gets context from options object if none was given', (done) => {
54 runTestCases([
55 {
56 input: `{{#get 'hash.a' a='some string'}}{{this}}{{/get}}`,
57 output: `some string`,
58 },
59 ], done);
60 });
61
62 1 it('renders to the empty string if no args are passed', (done) => {
63 runTestCases([
64 {
65 input: `{{get }}`,
66 output: ``,
67 }
68 ], done);
69 });
70
71 1 it('returns the object arg if it is a string, number, undefined, or null', (done) => {
72 runTestCases([
73 {
74 input: `{{get 'a' 'some string'}}`,
75 output: `some string`,
76 },
77 {
78 input: `{{get 'a' 42}}`,
79 output: `42`,
80 },
81 {
82 input: `{{get 'a' undefined}}`,
83 output: ``,
84 },
85 {
86 input: `{{get 'a' null}}`,
87 output: ``,
88 }
89 ], done);
90 });
91
92 1 it('returns undefined if prop path does not exist', (done) => {
93 runTestCases([
94 { // a key does not exist
95 input: `{{get 'z.z' options}}`,
96 output: ``,
97 },
98 { // first key exists, but its value is `undefined`
99 input:`{{get '0.zyx' (pluck (arrayify options) 'b')}}`, // array: [ undefined ]
100 output: ``,
101 }
102 ], done);
103 });
104 });

spec/helpers/getContentImage.js

100%
104
104
0
Line Lint Hits Source
1 1 const Lab = require('lab'),
2 lab = exports.lab = Lab.script(),
3 describe = lab.experiment,
4 it = lab.it,
5 specHelpers = require('../spec-helpers'),
6 testRunner = specHelpers.testRunner,
7 renderString = specHelpers.renderString;
8
9
10 1 describe('getContentImage helper', function() {
11 const siteSettings = {
12 cdn_url: 'https://cdn.bcapp/3dsf74g',
13 };
14 1 const context = {
15 object: {a:1}
16 };
17
18 1 const runTestCases = testRunner({context, siteSettings});
19
20
21 1 it('should throw an exception if a url is passed', function (done) {
22 renderString('{{getContentImage "http://example.com/image.jpg"}}').catch(e => {
23 done();
24 });
25 });
26
27 1 it('should throw an exception if a non-string is passed', function (done) {
28 renderString('{{getContentImage object}}').catch(e => {
29 renderString('{{getContentImage 123').catch(e => {
30 done();
31 });
32 });
33 });
34
35 1 it('should return an original image if no size is passed', function(done) {
36 runTestCases([
37 {
38 input: '{{getContentImage "asset.jpg"}}',
39 output: 'https://cdn.bcapp/3dsf74g/images/stencil/original/content/asset.jpg'
40 },
41 {
42 input: '{{getContentImage "folder/asset.jpg"}}',
43 output: 'https://cdn.bcapp/3dsf74g/images/stencil/original/content/folder/asset.jpg'
44 },
45 ], done);
46 });
47
48 1 it('should return an original image if invalid sizes are passed', function(done) {
49 runTestCases([
50 {
51 input: '{{getContentImage "asset.jpg" width="a"}}',
52 output: 'https://cdn.bcapp/3dsf74g/images/stencil/original/content/asset.jpg'
53 },
54 {
55 input: '{{getContentImage "asset.jpg" width="a" height="a"}}',
56 output: 'https://cdn.bcapp/3dsf74g/images/stencil/original/content/asset.jpg'
57 },
58 {
59 input: '{{getContentImage "asset.jpg" height=123}}',
60 output: 'https://cdn.bcapp/3dsf74g/images/stencil/original/content/asset.jpg'
61 },
62 {
63 input: '{{getContentImage "folder/asset.jpg" height=123}}',
64 output: 'https://cdn.bcapp/3dsf74g/images/stencil/original/content/folder/asset.jpg'
65 },
66 ], done);
67 });
68
69 1 it('should return a sized image', function(done) {
70 runTestCases([
71 {
72 input: '{{getContentImage "asset.jpg" width=123}}',
73 output: 'https://cdn.bcapp/3dsf74g/images/stencil/123w/content/asset.jpg'
74 },
75 {
76 input: '{{getContentImage "folder/asset.jpg" width=123}}',
77 output: 'https://cdn.bcapp/3dsf74g/images/stencil/123w/content/folder/asset.jpg'
78 },
79 {
80 input: '{{getContentImage "asset.jpg" width=123 height=321}}',
81 output: 'https://cdn.bcapp/3dsf74g/images/stencil/123x321/content/asset.jpg'
82 },
83 {
84 input: '{{getContentImage "folder/asset.jpg" width=123 height=321}}',
85 output: 'https://cdn.bcapp/3dsf74g/images/stencil/123x321/content/folder/asset.jpg'
86 },
87 ], done);
88 });
89
90 1 it('should support lossy compression parameter', function(done) {
91 runTestCases([
92 {
93 input: '{{getContentImage "asset.jpg" lossy=true}}',
94 output: 'https://cdn.bcapp/3dsf74g/images/stencil/original/content/asset.jpg?compression=lossy'
95 },
96 {
97 input: '{{getContentImage "asset.jpg" width=123 lossy=true}}',
98 output: 'https://cdn.bcapp/3dsf74g/images/stencil/123w/content/asset.jpg?compression=lossy'
99 },
100 {
101 input: '{{getContentImage "asset.jpg" width=123 height=321 lossy=true}}',
102 output: 'https://cdn.bcapp/3dsf74g/images/stencil/123x321/content/asset.jpg?compression=lossy'
103 },
104 {
105 input: '{{getContentImage "asset.jpg" lossy=false}}',
106 output: 'https://cdn.bcapp/3dsf74g/images/stencil/original/content/asset.jpg'
107 },
108 {
109 input: '{{getContentImage "asset.jpg"}}',
110 output: 'https://cdn.bcapp/3dsf74g/images/stencil/original/content/asset.jpg'
111 },
112 ], done);
113 });
114 });
115

spec/helpers/getContentImageSrcset.js

100%
56
56
0
Line Lint Hits Source
1 1 const Lab = require('lab'),
2 lab = exports.lab = Lab.script(),
3 describe = lab.experiment,
4 it = lab.it,
5 specHelpers = require('../spec-helpers'),
6 testRunner = specHelpers.testRunner,
7 renderString = specHelpers.renderString;
8
9
10 1 describe('getContentImageSrcset helper', function() {
11 const siteSettings = {
12 cdn_url: 'https://cdn.bcapp/3dsf74g',
13 };
14 1 const context = {
15 object: {a:1}
16 };
17
18 1 const runTestCases = testRunner({context, siteSettings});
19
20
21 1 it('should throw an exception if a url is passed', function (done) {
22 renderString('{{getContentImageSrcset "http://example.com/image.jpg"}}').catch(e => {
23 done();
24 });
25 });
26
27 1 it('should throw an exception if a non-string is passed', function (done) {
28 renderString('{{getContentImageSrcset object}}').catch(e => {
29 renderString('{{getContentImageSrcset 123').catch(e => {
30 done();
31 });
32 });
33 });
34
35 1 it('should return a valid srcset', function(done) {
36 runTestCases([
37 {
38 input: '{{getContentImageSrcset "asset.jpg"}}',
39 output: 'https://cdn.bcapp/3dsf74g/images/stencil/80w/content/asset.jpg 80w, https://cdn.bcapp/3dsf74g/images/stencil/160w/content/asset.jpg 160w, https://cdn.bcapp/3dsf74g/images/stencil/320w/content/asset.jpg 320w, https://cdn.bcapp/3dsf74g/images/stencil/640w/content/asset.jpg 640w, https://cdn.bcapp/3dsf74g/images/stencil/960w/content/asset.jpg 960w, https://cdn.bcapp/3dsf74g/images/stencil/1280w/content/asset.jpg 1280w, https://cdn.bcapp/3dsf74g/images/stencil/1920w/content/asset.jpg 1920w, https://cdn.bcapp/3dsf74g/images/stencil/2560w/content/asset.jpg 2560w'
40 },
41 {
42 input: '{{getContentImageSrcset "folder/asset.jpg" width=123}}',
43 output: 'https://cdn.bcapp/3dsf74g/images/stencil/80w/content/folder/asset.jpg 80w, https://cdn.bcapp/3dsf74g/images/stencil/160w/content/folder/asset.jpg 160w, https://cdn.bcapp/3dsf74g/images/stencil/320w/content/folder/asset.jpg 320w, https://cdn.bcapp/3dsf74g/images/stencil/640w/content/folder/asset.jpg 640w, https://cdn.bcapp/3dsf74g/images/stencil/960w/content/folder/asset.jpg 960w, https://cdn.bcapp/3dsf74g/images/stencil/1280w/content/folder/asset.jpg 1280w, https://cdn.bcapp/3dsf74g/images/stencil/1920w/content/folder/asset.jpg 1920w, https://cdn.bcapp/3dsf74g/images/stencil/2560w/content/folder/asset.jpg 2560w'
44 },
45 ], done);
46 });
47
48 1 it('should support lossy compression parameter', function(done) {
49 runTestCases([
50 {
51 input: '{{getContentImageSrcset "asset.jpg" lossy=true}}',
52 output: 'https://cdn.bcapp/3dsf74g/images/stencil/80w/content/asset.jpg?compression=lossy 80w, https://cdn.bcapp/3dsf74g/images/stencil/160w/content/asset.jpg?compression=lossy 160w, https://cdn.bcapp/3dsf74g/images/stencil/320w/content/asset.jpg?compression=lossy 320w, https://cdn.bcapp/3dsf74g/images/stencil/640w/content/asset.jpg?compression=lossy 640w, https://cdn.bcapp/3dsf74g/images/stencil/960w/content/asset.jpg?compression=lossy 960w, https://cdn.bcapp/3dsf74g/images/stencil/1280w/content/asset.jpg?compression=lossy 1280w, https://cdn.bcapp/3dsf74g/images/stencil/1920w/content/asset.jpg?compression=lossy 1920w, https://cdn.bcapp/3dsf74g/images/stencil/2560w/content/asset.jpg?compression=lossy 2560w'
53 },
54 {
55 input: '{{getContentImageSrcset "asset.jpg" lossy=false}}',
56 output: 'https://cdn.bcapp/3dsf74g/images/stencil/80w/content/asset.jpg 80w, https://cdn.bcapp/3dsf74g/images/stencil/160w/content/asset.jpg 160w, https://cdn.bcapp/3dsf74g/images/stencil/320w/content/asset.jpg 320w, https://cdn.bcapp/3dsf74g/images/stencil/640w/content/asset.jpg 640w, https://cdn.bcapp/3dsf74g/images/stencil/960w/content/asset.jpg 960w, https://cdn.bcapp/3dsf74g/images/stencil/1280w/content/asset.jpg 1280w, https://cdn.bcapp/3dsf74g/images/stencil/1920w/content/asset.jpg 1920w, https://cdn.bcapp/3dsf74g/images/stencil/2560w/content/asset.jpg 2560w'
57 },
58 {
59 input: '{{getContentImageSrcset "asset.jpg"}}',
60 output: 'https://cdn.bcapp/3dsf74g/images/stencil/80w/content/asset.jpg 80w, https://cdn.bcapp/3dsf74g/images/stencil/160w/content/asset.jpg 160w, https://cdn.bcapp/3dsf74g/images/stencil/320w/content/asset.jpg 320w, https://cdn.bcapp/3dsf74g/images/stencil/640w/content/asset.jpg 640w, https://cdn.bcapp/3dsf74g/images/stencil/960w/content/asset.jpg 960w, https://cdn.bcapp/3dsf74g/images/stencil/1280w/content/asset.jpg 1280w, https://cdn.bcapp/3dsf74g/images/stencil/1920w/content/asset.jpg 1920w, https://cdn.bcapp/3dsf74g/images/stencil/2560w/content/asset.jpg 2560w'
61 },
62 ], done);
63 });
64 });
65

spec/helpers/getFontLoaderConfig.js

100%
32
32
0
Line Lint Hits Source
1 1 const Lab = require('lab'),
2 lab = exports.lab = Lab.script(),
3 describe = lab.experiment,
4 it = lab.it,
5 testRunner = require('../spec-helpers').testRunner;
6
7 1 describe('getFontLoaderConfig', function () {
8 it('should return the expected font config', function (done) {
9 const themeSettings = {
10 'test1-font': 'Google_Open+Sans',
11 'test2-font': 'Google_Open+Sans_400italic',
12 'test3-font': 'Google_Open+Sans_700',
13 'test4-font': 'Google_Karla_700',
14 'test5-font': 'Google_Lora_400_sans',
15 'test6-font': 'Google_Volkron',
16 'test7-font': 'Google_Droid_400,700',
17 'test8-font': 'Google_Crimson+Text_400,700_sans',
18 'random-property': 'not a font'
19 };
20
21 1 const expectedConfig = {
22 google: {
23 families: ['Open Sans:,400italic,700', 'Karla:700', 'Lora:400', 'Volkron:', 'Droid:400,700', 'Crimson Text:400,700']
24 }
25 };
26
27 1 const runTestCases = testRunner({themeSettings});
28 1 runTestCases([
29 {
30 input: '{{getFontLoaderConfig}}',
31 output: JSON.stringify(expectedConfig),
32 },
33 ], done);
34 });
35 });
36

spec/helpers/getFontsCollection.js

100%
126
126
0
Line Lint Hits Source
1 1 const Lab = require('lab'),
2 lab = exports.lab = Lab.script(),
3 describe = lab.experiment,
4 it = lab.it,
5 {testRunner, buildRenderer} = require('../spec-helpers');
6 1 const {expect} = require("code");
7 1 const {resourceHintAllowedTypes, resourceHintAllowedStates} = require('../../helpers/lib/resourceHints');
8
9 1 describe('getFontsCollection', function () {
10 it('should return a font link with fonts from theme settings and &display=swap when no font-display value is passed', function (done) {
11 const themeSettings = {
12 'test1-font': 'Google_Open+Sans',
13 'test2-font': 'Google_Open+Sans_400italic',
14 'test3-font': 'Google_Open+Sans_700',
15 'test4-font': 'Google_Karla_700',
16 'test5-font': 'Google_Lora_400_sans',
17 'test6-font': 'Google_Volkron',
18 'test7-font': 'Google_Droid_400,700',
19 'test8-font': 'Google_Crimson+Text_400,700_sans',
20 'random-property': 'not a font'
21 };
22 1 const renderer = buildRenderer({}, themeSettings);
23 1 const runTestCases = testRunner({renderer});
24 1 const href = "https://fonts.googleapis.com/css?family=Open+Sans:,400italic,700%7CKarla:700%7CLora:400%7CVolkron:%7CDroid:400,700%7CCrimson+Text:400,700&display=swap";
25 1 runTestCases([
26 {
27 input: '{{getFontsCollection}}',
28 output: `<link href="${href}" rel="stylesheet">`,
29 },
30 ], () => {
31 const hints = renderer.getResourceHints();
32 1 expect(hints).to.have.length(1);
33 1 expect(hints[0].src).to.equals(href);
34 1 expect(hints[0].state).to.equals(resourceHintAllowedStates.preloadResourceHintState);
35 1 expect(hints[0].type).to.equals(resourceHintAllowedTypes.resourceHintStyleType);
36 1 done();
37 });
38 });
39 1 it('should return a font link with fonts from theme settings and &display=swap when font-display value passed is invalid', function (done) {
40 const themeSettings = {
41 'test1-font': 'Google_Open+Sans',
42 'test2-font': 'Google_Open+Sans_400italic',
43 'test3-font': 'Google_Open+Sans_700',
44 'test4-font': 'Google_Karla_700',
45 'test5-font': 'Google_Lora_400_sans',
46 'test6-font': 'Google_Volkron',
47 'test7-font': 'Google_Droid_400,700',
48 'test8-font': 'Google_Crimson+Text_400,700_sans',
49 'random-property': 'not a font'
50 };
51
52 1 const runTestCases = testRunner({themeSettings});
53 1 runTestCases([
54 {
55 input: '{{getFontsCollection font-display="oreo"}}',
56 output: '<link href="https://fonts.googleapis.com/css?family=Open+Sans:,400italic,700%7CKarla:700%7CLora:400%7CVolkron:%7CDroid:400,700%7CCrimson+Text:400,700&display=swap" rel="stylesheet">',
57 },
58 ], done);
59 });
60 1 it('should return a font link with fonts from theme settings and &display=${font-display} when valid font-display value is passed', function (done) {
61 const themeSettings = {
62 'test1-font': 'Google_Open+Sans',
63 'test2-font': 'Google_Open+Sans_400italic',
64 'test3-font': 'Google_Open+Sans_700',
65 'test4-font': 'Google_Karla_700',
66 'test5-font': 'Google_Lora_400_sans',
67 'test6-font': 'Google_Volkron',
68 'test7-font': 'Google_Droid_400,700',
69 'test8-font': 'Google_Crimson+Text_400,700_sans',
70 'random-property': 'not a font'
71 };
72
73 1 const runTestCases = testRunner({themeSettings});
74 1 runTestCases([
75 {
76 input: '{{getFontsCollection font-display="fallback"}}',
77 output: '<link href="https://fonts.googleapis.com/css?family=Open+Sans:,400italic,700%7CKarla:700%7CLora:400%7CVolkron:%7CDroid:400,700%7CCrimson+Text:400,700&display=fallback" rel="stylesheet">',
78 },
79 ], done);
80 });
81 1 it('should not crash if a malformed Google font is passed when no font-display value is passed', function (done) {
82 const themeSettings = {
83 'test1-font': 'Google_Open+Sans',
84 'test2-font': 'Google_',
85 'test3-font': 'Google'
86 };
87 1 const renderer = buildRenderer({}, themeSettings);
88 1 const runTestCases = testRunner({renderer});
89 1 runTestCases([
90 {
91 input: '{{getFontsCollection}}',
92 output: '<link href="https://fonts.googleapis.com/css?family=Open+Sans:&display=swap" rel="stylesheet">',
93 },
94 ], () => {
95 const hints = renderer.getResourceHints();
96 1 expect(hints).to.have.length(1);
97 1 expect(hints[0].state).to.equals(resourceHintAllowedStates.preloadResourceHintState);
98 1 done();
99 });
100 });
101 1 it('should not crash if a malformed Google font is passed when valid font-display value is passed', function (done) {
102 const themeSettings = {
103 'test1-font': 'Google_Open+Sans',
104 'test2-font': 'Google_',
105 'test3-font': 'Google'
106 };
107
108 1 const runTestCases = testRunner({themeSettings});
109 1 runTestCases([
110 {
111 input: '{{getFontsCollection font-display="fallback"}}',
112 output: '<link href="https://fonts.googleapis.com/css?family=Open+Sans:&display=fallback" rel="stylesheet">',
113 },
114 ], done);
115 });
116 1 it('should not crash if a malformed Google font is passed when invalid font-display value is passed', function (done) {
117 const themeSettings = {
118 'test1-font': 'Google_Open+Sans',
119 'test2-font': 'Google_',
120 'test3-font': 'Google'
121 };
122
123 1 const runTestCases = testRunner({themeSettings});
124 1 runTestCases([
125 {
126 input: '{{getFontsCollection font-display="oreo"}}',
127 output: '<link href="https://fonts.googleapis.com/css?family=Open+Sans:&display=swap" rel="stylesheet">',
128 },
129 ], done);
130 });
131 });
132

spec/helpers/getImage.js

100%
156
156
0
Line Lint Hits Source
1 1 const Lab = require('lab'),
2 lab = exports.lab = Lab.script(),
3 describe = lab.experiment,
4 it = lab.it,
5 testRunner = require('../spec-helpers').testRunner;
6
7 1 const themeSettings = {
8 logo_image: '600x300',
9 gallery: '100x100',
10 _images: {
11 logo: {
12 width: 250,
13 height: 100
14 },
15 gallery: {
16 width: 300,
17 height: 300
18 },
19 missing_values: {},
20 missing_width: {height: 100}
21 }
22 };
23
24 1 describe('getImage helper', function() {
25 const urlData = 'https://cdn.example.com/path/to/{:size}/image.png?c=2';
26 1 const urlData_2_qs = 'https://cdn.example.com/path/to/{:size}/image.png?c=2&imbypass=on';
27 1 const context = {
28 image_url: 'http://example.com/image.png',
29 not_an_image: null,
30 image: {
31 data: urlData,
32 width: null,
33 height: null,
34 },
35 image_with_dimensions: {
36 data: urlData,
37 width: 123,
38 height: 123,
39 },
40 image_with_2_qs: {
41 data: urlData_2_qs
42 },
43 logoPreset: 'logo',
44 };
45
46 1 const runTestCases = testRunner({context, themeSettings});
47
48 1 it('should return a url if a url is passed', function(done) {
49 runTestCases([
50 {
51 input: '{{getImage "http://example.com/image.jpg"}}',
52 output: 'http://example.com/image.jpg',
53 },
54 ], done);
55 });
56
57 1 it('should return empty if image is invalid', function(done) {
58 runTestCases([
59 {
60 input: '{{getImage not_existing_image}}',
61 output: '',
62 },
63 {
64 input: '{{getImage not_an_image}}',
65 output: '',
66 },
67 ], done);
68 });
69
70 1 it('should use the preset from _images', function(done) {
71 runTestCases([
72 {
73 input: '{{getImage image "logo"}}',
74 output: urlData.replace('{:size}', '250x100'),
75 },
76 {
77 input: '{{getImage image_with_2_qs "logo"}}',
78 output: urlData_2_qs.replace('{:size}', '250x100'),
79 },
80 {
81 input: '{{getImage image "gallery"}}',
82 output: urlData.replace('{:size}', '300x300'),
83 },
84 {
85 input: '{{getImage image_with_2_qs "gallery"}}',
86 output: urlData_2_qs.replace('{:size}', '300x300'),
87 },
88 ], done);
89 });
90
91 1 it('should use the size from the theme_settings', function(done) {
92 runTestCases([
93 {
94 input: '{{getImage image "logo_image"}}',
95 output: urlData.replace('{:size}', '600x300'),
96 },
97 ], done);
98 });
99
100 1 it('should use the default image url if image is invalid', function(done) {
101 runTestCases([
102 {
103 input: '{{getImage not_an_image "logo" "http://image"}}',
104 output: 'http://image',
105 },
106 {
107 input: '{{getImage not_an_image "logo" image_url}}',
108 output: context.image_url,
109 },
110 ], done);
111 });
112
113 1 it('should use original size if not default is passed', function(done) {
114 runTestCases([
115 {
116 input: '{{getImage image "bad_preset"}}',
117 output: urlData.replace('{:size}', 'original'),
118 },
119 ], done);
120 });
121
122 1 it('should default to max value (width & height) if value is not provided', function(done) {
123 runTestCases([
124 {
125 input: '{{getImage image "missing_values"}}',
126 output: urlData.replace('{:size}', '5120x5120'),
127 },
128 {
129 input: '{{getImage image "missing_width"}}',
130 output: urlData.replace('{:size}', '5120x100'),
131 },
132 ], done);
133 });
134
135 1 it('should default to size of the image dimensions if known and a larger size is requested', function(done) {
136 runTestCases([
137 {
138 input: '{{getImage image_with_dimensions "logo"}}',
139 output: urlData.replace('{:size}', '123x100'),
140 },
141 {
142 input: '{{getImage image_with_dimensions "logo_image"}}',
143 output: urlData.replace('{:size}', '123x123'),
144 },
145 ], done);
146 });
147
148 1 it('should support lossy compression parameter', function(done) {
149 runTestCases([
150 {
151 input: '{{getImage image "logo" lossy=true}}',
152 output: urlData.replace('{:size}', '250x100') + '&compression=lossy',
153 },
154 {
155 input: '{{getImage image "logo" lossy=false}}',
156 output: urlData.replace('{:size}', '250x100'),
157 },
158 {
159 input: '{{getImage image "logo"}}',
160 output: urlData.replace('{:size}', '250x100'),
161 },
162 {
163 input: '{{getImage image_with_2_qs "logo" lossy=true}}',
164 output: urlData_2_qs.replace('{:size}', '250x100') + '&compression=lossy',
165 },
166 ], done);
167 });
168 });
169

spec/helpers/getImageManagerImage.js

100%
104
104
0
Line Lint Hits Source
1 1 const Lab = require('lab'),
2 lab = exports.lab = Lab.script(),
3 describe = lab.experiment,
4 it = lab.it,
5 specHelpers = require('../spec-helpers'),
6 testRunner = specHelpers.testRunner,
7 renderString = specHelpers.renderString;
8
9
10 1 describe('getImageManagerImage helper', function() {
11 const siteSettings = {
12 cdn_url: 'https://cdn.bcapp/3dsf74g',
13 };
14 1 const context = {
15 object: {a:1}
16 };
17
18 1 const runTestCases = testRunner({context, siteSettings});
19
20
21 1 it('should throw an exception if a url is passed', function (done) {
22 renderString('{{getImageManagerImage "http://example.com/image.jpg"}}').catch(e => {
23 done();
24 });
25 });
26
27 1 it('should throw an exception if a non-string is passed', function (done) {
28 renderString('{{getImageManagerImage object}}').catch(e => {
29 renderString('{{getImageManagerImage 123').catch(e => {
30 done();
31 });
32 });
33 });
34
35 1 it('should return an original image if no size is passed', function(done) {
36 runTestCases([
37 {
38 input: '{{getImageManagerImage "asset.jpg"}}',
39 output: 'https://cdn.bcapp/3dsf74g/images/stencil/original/image-manager/asset.jpg'
40 },
41 {
42 input: '{{getImageManagerImage "folder/asset.jpg"}}',
43 output: 'https://cdn.bcapp/3dsf74g/images/stencil/original/image-manager/folder/asset.jpg'
44 },
45 ], done);
46 });
47
48 1 it('should return an original image if invalid sizes are passed', function(done) {
49 runTestCases([
50 {
51 input: '{{getImageManagerImage "asset.jpg" width="a"}}',
52 output: 'https://cdn.bcapp/3dsf74g/images/stencil/original/image-manager/asset.jpg'
53 },
54 {
55 input: '{{getImageManagerImage "asset.jpg" width="a" height="a"}}',
56 output: 'https://cdn.bcapp/3dsf74g/images/stencil/original/image-manager/asset.jpg'
57 },
58 {
59 input: '{{getImageManagerImage "asset.jpg" height=123}}',
60 output: 'https://cdn.bcapp/3dsf74g/images/stencil/original/image-manager/asset.jpg'
61 },
62 {
63 input: '{{getImageManagerImage "folder/asset.jpg" height=123}}',
64 output: 'https://cdn.bcapp/3dsf74g/images/stencil/original/image-manager/folder/asset.jpg'
65 },
66 ], done);
67 });
68
69 1 it('should return a sized image', function(done) {
70 runTestCases([
71 {
72 input: '{{getImageManagerImage "asset.jpg" width=123}}',
73 output: 'https://cdn.bcapp/3dsf74g/images/stencil/123w/image-manager/asset.jpg'
74 },
75 {
76 input: '{{getImageManagerImage "folder/asset.jpg" width=123}}',
77 output: 'https://cdn.bcapp/3dsf74g/images/stencil/123w/image-manager/folder/asset.jpg'
78 },
79 {
80 input: '{{getImageManagerImage "asset.jpg" width=123 height=321}}',
81 output: 'https://cdn.bcapp/3dsf74g/images/stencil/123x321/image-manager/asset.jpg'
82 },
83 {
84 input: '{{getImageManagerImage "folder/asset.jpg" width=123 height=321}}',
85 output: 'https://cdn.bcapp/3dsf74g/images/stencil/123x321/image-manager/folder/asset.jpg'
86 },
87 ], done);
88 });
89
90 1 it('should support lossy compression parameter', function(done) {
91 runTestCases([
92 {
93 input: '{{getImageManagerImage "asset.jpg" lossy=true}}',
94 output: 'https://cdn.bcapp/3dsf74g/images/stencil/original/image-manager/asset.jpg?compression=lossy'
95 },
96 {
97 input: '{{getImageManagerImage "asset.jpg" width=123 lossy=true}}',
98 output: 'https://cdn.bcapp/3dsf74g/images/stencil/123w/image-manager/asset.jpg?compression=lossy'
99 },
100 {
101 input: '{{getImageManagerImage "asset.jpg" width=123 height=321 lossy=true}}',
102 output: 'https://cdn.bcapp/3dsf74g/images/stencil/123x321/image-manager/asset.jpg?compression=lossy'
103 },
104 {
105 input: '{{getImageManagerImage "asset.jpg" lossy=false}}',
106 output: 'https://cdn.bcapp/3dsf74g/images/stencil/original/image-manager/asset.jpg'
107 },
108 {
109 input: '{{getImageManagerImage "asset.jpg"}}',
110 output: 'https://cdn.bcapp/3dsf74g/images/stencil/original/image-manager/asset.jpg'
111 },
112 ], done);
113 });
114 });
115

spec/helpers/getImageManagerImageSrcset.js

100%
56
56
0
Line Lint Hits Source
1 1 const Lab = require('lab'),
2 lab = exports.lab = Lab.script(),
3 describe = lab.experiment,
4 it = lab.it,
5 specHelpers = require('../spec-helpers'),
6 testRunner = specHelpers.testRunner,
7 renderString = specHelpers.renderString;
8
9
10 1 describe('getImageManagerImageSrcset helper', function() {
11 const siteSettings = {
12 cdn_url: 'https://cdn.bcapp/3dsf74g',
13 };
14 1 const context = {
15 object: {a:1}
16 };
17
18 1 const runTestCases = testRunner({context, siteSettings});
19
20
21 1 it('should throw an exception if a url is passed', function (done) {
22 renderString('{{getImageManagerImageSrcset "http://example.com/image.jpg"}}').catch(e => {
23 done();
24 });
25 });
26
27 1 it('should throw an exception if a non-string is passed', function (done) {
28 renderString('{{getImageManagerImageSrcset object}}').catch(e => {
29 renderString('{{getImageManagerImageSrcset 123').catch(e => {
30 done();
31 });
32 });
33 });
34
35 1 it('should return a valid srcset', function(done) {
36 runTestCases([
37 {
38 input: '{{getImageManagerImageSrcset "asset.jpg"}}',
39 output: 'https://cdn.bcapp/3dsf74g/images/stencil/80w/image-manager/asset.jpg 80w, https://cdn.bcapp/3dsf74g/images/stencil/160w/image-manager/asset.jpg 160w, https://cdn.bcapp/3dsf74g/images/stencil/320w/image-manager/asset.jpg 320w, https://cdn.bcapp/3dsf74g/images/stencil/640w/image-manager/asset.jpg 640w, https://cdn.bcapp/3dsf74g/images/stencil/960w/image-manager/asset.jpg 960w, https://cdn.bcapp/3dsf74g/images/stencil/1280w/image-manager/asset.jpg 1280w, https://cdn.bcapp/3dsf74g/images/stencil/1920w/image-manager/asset.jpg 1920w, https://cdn.bcapp/3dsf74g/images/stencil/2560w/image-manager/asset.jpg 2560w'
40 },
41 {
42 input: '{{getImageManagerImageSrcset "folder/asset.jpg" width=123}}',
43 output: 'https://cdn.bcapp/3dsf74g/images/stencil/80w/image-manager/folder/asset.jpg 80w, https://cdn.bcapp/3dsf74g/images/stencil/160w/image-manager/folder/asset.jpg 160w, https://cdn.bcapp/3dsf74g/images/stencil/320w/image-manager/folder/asset.jpg 320w, https://cdn.bcapp/3dsf74g/images/stencil/640w/image-manager/folder/asset.jpg 640w, https://cdn.bcapp/3dsf74g/images/stencil/960w/image-manager/folder/asset.jpg 960w, https://cdn.bcapp/3dsf74g/images/stencil/1280w/image-manager/folder/asset.jpg 1280w, https://cdn.bcapp/3dsf74g/images/stencil/1920w/image-manager/folder/asset.jpg 1920w, https://cdn.bcapp/3dsf74g/images/stencil/2560w/image-manager/folder/asset.jpg 2560w'
44 },
45 ], done);
46 });
47
48 1 it('should support lossy compression parameter', function(done) {
49 runTestCases([
50 {
51 input: '{{getImageManagerImageSrcset "asset.jpg" lossy=true}}',
52 output: 'https://cdn.bcapp/3dsf74g/images/stencil/80w/image-manager/asset.jpg?compression=lossy 80w, https://cdn.bcapp/3dsf74g/images/stencil/160w/image-manager/asset.jpg?compression=lossy 160w, https://cdn.bcapp/3dsf74g/images/stencil/320w/image-manager/asset.jpg?compression=lossy 320w, https://cdn.bcapp/3dsf74g/images/stencil/640w/image-manager/asset.jpg?compression=lossy 640w, https://cdn.bcapp/3dsf74g/images/stencil/960w/image-manager/asset.jpg?compression=lossy 960w, https://cdn.bcapp/3dsf74g/images/stencil/1280w/image-manager/asset.jpg?compression=lossy 1280w, https://cdn.bcapp/3dsf74g/images/stencil/1920w/image-manager/asset.jpg?compression=lossy 1920w, https://cdn.bcapp/3dsf74g/images/stencil/2560w/image-manager/asset.jpg?compression=lossy 2560w'
53 },
54 {
55 input: '{{getImageManagerImageSrcset "asset.jpg" lossy=false}}',
56 output: 'https://cdn.bcapp/3dsf74g/images/stencil/80w/image-manager/asset.jpg 80w, https://cdn.bcapp/3dsf74g/images/stencil/160w/image-manager/asset.jpg 160w, https://cdn.bcapp/3dsf74g/images/stencil/320w/image-manager/asset.jpg 320w, https://cdn.bcapp/3dsf74g/images/stencil/640w/image-manager/asset.jpg 640w, https://cdn.bcapp/3dsf74g/images/stencil/960w/image-manager/asset.jpg 960w, https://cdn.bcapp/3dsf74g/images/stencil/1280w/image-manager/asset.jpg 1280w, https://cdn.bcapp/3dsf74g/images/stencil/1920w/image-manager/asset.jpg 1920w, https://cdn.bcapp/3dsf74g/images/stencil/2560w/image-manager/asset.jpg 2560w'
57 },
58 {
59 input: '{{getImageManagerImageSrcset "asset.jpg"}}',
60 output: 'https://cdn.bcapp/3dsf74g/images/stencil/80w/image-manager/asset.jpg 80w, https://cdn.bcapp/3dsf74g/images/stencil/160w/image-manager/asset.jpg 160w, https://cdn.bcapp/3dsf74g/images/stencil/320w/image-manager/asset.jpg 320w, https://cdn.bcapp/3dsf74g/images/stencil/640w/image-manager/asset.jpg 640w, https://cdn.bcapp/3dsf74g/images/stencil/960w/image-manager/asset.jpg 960w, https://cdn.bcapp/3dsf74g/images/stencil/1280w/image-manager/asset.jpg 1280w, https://cdn.bcapp/3dsf74g/images/stencil/1920w/image-manager/asset.jpg 1920w, https://cdn.bcapp/3dsf74g/images/stencil/2560w/image-manager/asset.jpg 2560w'
61 },
62 ], done);
63 });
64 });
65

spec/helpers/getImageSrcset.js

100%
151
151
0
Line Lint Hits Source
1 1 const Lab = require('lab'),
2 lab = exports.lab = Lab.script(),
3 describe = lab.experiment,
4 it = lab.it,
5 testRunner = require('../spec-helpers').testRunner;
6
7 1 describe('getImageSrcset helper', function() {
8 const urlData = 'https://cdn.example.com/path/to/{:size}/image.png?c=2';
9 1 const urlData_2_qs = 'https://cdn.example.com/path/to/{:size}/image.png?c=2&imbypass=on';
10 1 const context = {
11 image_url: 'http://example.com/image.png',
12 not_an_image: null,
13 image: {
14 data: urlData
15 },
16 image_with_2_qs: {
17 data: urlData_2_qs,
18 width: null,
19 height: null,
20 },
21 image_with_dimensions: {
22 data: urlData,
23 width: 1400,
24 height: 900,
25 },
26 };
27
28 1 const runTestCases = testRunner({context});
29
30 1 it('should return a srcset if a valid image and srcset sizes are passed', function(done) {
31 runTestCases([
32 {
33 input: '{{getImageSrcset image 100w="100w" 200w="200w" 300w="300w" 1000w="1000w"}}',
34 output: 'https://cdn.example.com/path/to/100w/image.png?c=2 100w, https://cdn.example.com/path/to/200w/image.png?c=2 200w, https://cdn.example.com/path/to/300w/image.png?c=2 300w, https://cdn.example.com/path/to/1000w/image.png?c=2 1000w',
35 },
36 {
37 input: '{{getImageSrcset image_with_2_qs 100w="100w" 200w="200w" 300w="300w" 1000w="1000w"}}',
38 output: 'https://cdn.example.com/path/to/100w/image.png?c=2&imbypass=on 100w, https://cdn.example.com/path/to/200w/image.png?c=2&imbypass=on 200w, https://cdn.example.com/path/to/300w/image.png?c=2&imbypass=on 300w, https://cdn.example.com/path/to/1000w/image.png?c=2&imbypass=on 1000w',
39 },
40 {
41 input: '{{getImageSrcset image 1x="768x768" 2x="1536x1536"}}',
42 output: 'https://cdn.example.com/path/to/768x768/image.png?c=2 1x, https://cdn.example.com/path/to/1536x1536/image.png?c=2 2x',
43 },
44 ], done);
45 });
46
47 1 it('should return a srcset made of default sizes if requested', function(done) {
48 runTestCases([
49 {
50 input: '{{getImageSrcset image use_default_sizes=true}}',
51 output: 'https://cdn.example.com/path/to/80w/image.png?c=2 80w, https://cdn.example.com/path/to/160w/image.png?c=2 160w, https://cdn.example.com/path/to/320w/image.png?c=2 320w, https://cdn.example.com/path/to/640w/image.png?c=2 640w, https://cdn.example.com/path/to/960w/image.png?c=2 960w, https://cdn.example.com/path/to/1280w/image.png?c=2 1280w, https://cdn.example.com/path/to/1920w/image.png?c=2 1920w, https://cdn.example.com/path/to/2560w/image.png?c=2 2560w',
52 },
53 ], done);
54 });
55
56 1 it('should return a srcset made of default sizes up to the width of the image if known', function(done) {
57 runTestCases([
58 {
59 input: '{{getImageSrcset image_with_dimensions use_default_sizes=true}}',
60 output: 'https://cdn.example.com/path/to/80w/image.png?c=2 80w, https://cdn.example.com/path/to/160w/image.png?c=2 160w, https://cdn.example.com/path/to/320w/image.png?c=2 320w, https://cdn.example.com/path/to/640w/image.png?c=2 640w, https://cdn.example.com/path/to/960w/image.png?c=2 960w, https://cdn.example.com/path/to/1280w/image.png?c=2 1280w, https://cdn.example.com/path/to/1400w/image.png?c=2 1400w',
61 },
62 ], done);
63 });
64
65 1 it('should return empty string if no parameters are passed', function(done) {
66 runTestCases([
67 {
68 input: '{{getImageSrcset image}}',
69 output: '',
70 },
71 ], done);
72 });
73
74 1 it('should return a srcset without a descriptor if a valid image and single srcset size is passed', function(done) {
75 runTestCases([
76 {
77 input: '{{getImageSrcset image 100w="100w"}}',
78 output: 'https://cdn.example.com/path/to/100w/image.png?c=2',
79 },
80 {
81 input: '{{getImageSrcset image 1x="768x768"}}',
82 output: 'https://cdn.example.com/path/to/768x768/image.png?c=2',
83 },
84 ], done);
85 });
86
87
88 1 it('should return a url if a url is passed', function(done) {
89 runTestCases([
90 {
91 input: '{{getImageSrcset "http://example.com/image.jpg"}}',
92 output: 'http://example.com/image.jpg',
93 },
94 {
95 input: '{{getImageSrcset "https://example.com/image.jpg"}}',
96 output: 'https://example.com/image.jpg',
97 },
98 ], done);
99 });
100
101 1 it('should return empty if srcset array is invalid', function(done) {
102 runTestCases([
103 {
104 input: '{{getImageSrcset image 100="100w"}}',
105 output: '',
106 },
107 {
108 input: '{{getImageSrcset image abc="def"}}',
109 output: '',
110 },
111 ], done);
112 });
113
114 1 it('should return empty if image is invalid', function(done) {
115 runTestCases([
116 {
117 input: '{{getImageSrcset not_existing_image 100w="100w" 200w="200w"}}',
118 output: '',
119 },
120 {
121 input: '{{getImageSrcset not_an_image 100w="100w" 200w="200w"}}',
122 output: '',
123 },
124 ], done);
125 });
126
127 1 it('should use the default image url if image is invalid', function(done) {
128 runTestCases([
129 {
130 input: '{{getImageSrcset not_an_image "http://image" 100w="100w" 200w="200w"}}',
131 output: 'http://image',
132 },
133 {
134 input: '{{getImageSrcset not_an_image image_url 100w="100w" 200w="200w"}}',
135 output: context.image_url,
136 },
137 ], done);
138 });
139
140 1 it('should support lossy compression parameter', function(done) {
141 runTestCases([
142 {
143 input: '{{getImageSrcset image 100w="100w" 200w="200w" lossy=true}}',
144 output: 'https://cdn.example.com/path/to/100w/image.png?c=2&compression=lossy 100w, https://cdn.example.com/path/to/200w/image.png?c=2&compression=lossy 200w',
145 },
146 {
147 input: '{{getImageSrcset image_with_2_qs 100w="100w" 200w="200w" lossy=true}}',
148 output: 'https://cdn.example.com/path/to/100w/image.png?c=2&imbypass=on&compression=lossy 100w, https://cdn.example.com/path/to/200w/image.png?c=2&imbypass=on&compression=lossy 200w',
149 },
150 {
151 input: '{{getImageSrcset image 100w="100w" lossy=true}}',
152 output: 'https://cdn.example.com/path/to/100w/image.png?c=2&compression=lossy',
153 },
154 {
155 input: '{{getImageSrcset image 100w="100w" lossy=false}}',
156 output: 'https://cdn.example.com/path/to/100w/image.png?c=2',
157 },
158 {
159 input: '{{getImageSrcset image 100w="100w"}}',
160 output: 'https://cdn.example.com/path/to/100w/image.png?c=2',
161 },
162 ], done);
163 });
164 });
165

spec/helpers/getImageSrcset1x2x.js

100%
105
105
0
Line Lint Hits Source
1 1 const Lab = require('lab'),
2 lab = exports.lab = Lab.script(),
3 describe = lab.experiment,
4 it = lab.it,
5 specHelpers = require('../spec-helpers'),
6 testRunner = specHelpers.testRunner,
7 renderString = specHelpers.renderString;
8
9 1 describe('getImageSrcset1x2x helper', function() {
10 const urlData = 'https://cdn.example.com/path/to/{:size}/image.png?c=2';
11 1 const context = {
12 image_size_small: "123x456",
13 image_size_large: "1000x900",
14 image_url: 'http://example.com/image.png',
15 not_an_image: null,
16 image_without_dimensions: {
17 data: urlData
18 },
19 image_with_null_dimensions: {
20 data: urlData,
21 width: null,
22 height: null,
23 },
24 image_with_dimensions: {
25 data: urlData,
26 width: 1400,
27 height: 950,
28 },
29 image_with_large_dimensions: {
30 data: urlData,
31 width: 5200,
32 height: 5200,
33 },
34 };
35
36 1 const runTestCases = testRunner({context});
37
38 1 it('should return a srcset if a valid image and srcset sizes are passed', function(done) {
39 runTestCases([
40 {
41 input: '{{getImageSrcset1x2x image_with_dimensions "123x456"}}',
42 output: 'https://cdn.example.com/path/to/123x456/image.png?c=2 1x, https://cdn.example.com/path/to/246x912/image.png?c=2 2x',
43 },
44 {
45 input: '{{getImageSrcset1x2x image_with_dimensions "123x556"}}',
46 output: 'https://cdn.example.com/path/to/123x556/image.png?c=2 1x, https://cdn.example.com/path/to/210x950/image.png?c=2 1.7086x',
47 },
48 {
49 input: '{{getImageSrcset1x2x image_with_dimensions image_size_small}}',
50 output: 'https://cdn.example.com/path/to/123x456/image.png?c=2 1x, https://cdn.example.com/path/to/246x912/image.png?c=2 2x',
51 },
52 ], done);
53 });
54
55 1 it('should return an image url alone (1x) if the 2x size is not within constraints', function(done) {
56 runTestCases([
57 {
58 input: '{{getImageSrcset1x2x image_with_dimensions "1000x900"}}',
59 output: 'https://cdn.example.com/path/to/1000x900/image.png?c=2',
60 },
61 {
62 input: '{{getImageSrcset1x2x image_with_dimensions image_size_large}}',
63 output: 'https://cdn.example.com/path/to/1000x900/image.png?c=2',
64 },
65 {
66 input: '{{getImageSrcset1x2x image_with_large_dimensions "2600x2600"}}',
67 output: 'https://cdn.example.com/path/to/2600x2600/image.png?c=2',
68 },
69 ], done);
70 });
71
72 1 it('should return an image url alone (1x) if image does not have dimesions', function(done) {
73 runTestCases([
74 {
75 input: '{{getImageSrcset1x2x image_with_null_dimensions "1000x900"}}',
76 output: 'https://cdn.example.com/path/to/1000x900/image.png?c=2',
77 },
78 {
79 input: '{{getImageSrcset1x2x image_without_dimensions "1000x900"}}',
80 output: 'https://cdn.example.com/path/to/1000x900/image.png?c=2',
81 },
82 ], done);
83 });
84
85 1 it('should throw an exception if an no size argument is passed', function (done) {
86 renderString('{{getImageSrcset1x2x image_with_dimensions}}').catch(e => {
87 done();
88 });
89 });
90
91 1 it('should throw an exception if an invalid size argument is passed', function (done) {
92 renderString('{{getImageSrcset1x2x image_with_dimensions "100px"}}').catch(e => {
93 done();
94 });
95 });
96
97 1 it('should support lossy compression parameter', function(done) {
98 runTestCases([
99 {
100 input: '{{getImageSrcset1x2x image_with_dimensions "123x456" lossy=true}}',
101 output: 'https://cdn.example.com/path/to/123x456/image.png?c=2&compression=lossy 1x, https://cdn.example.com/path/to/246x912/image.png?c=2&compression=lossy 2x',
102 },
103 {
104 input: '{{getImageSrcset1x2x image_with_dimensions "123x456" lossy=false}}',
105 output: 'https://cdn.example.com/path/to/123x456/image.png?c=2 1x, https://cdn.example.com/path/to/246x912/image.png?c=2 2x',
106 },
107 {
108 input: '{{getImageSrcset1x2x image_with_dimensions "123x456"}}',
109 output: 'https://cdn.example.com/path/to/123x456/image.png?c=2 1x, https://cdn.example.com/path/to/246x912/image.png?c=2 2x',
110 },
111 ], done);
112 });
113 });
114

spec/helpers/getObject.js

100%
53
53
0
Line Lint Hits Source
1 1 const Lab = require('lab'),
2 lab = exports.lab = Lab.script(),
3 describe = lab.experiment,
4 it = lab.it,
5 testRunner = require('../spec-helpers').testRunner;
6
7 1 describe('getObject helper', function () {
8 const context = {
9 obj: {
10 a: {
11 b: {
12 c: 'd'
13 },
14 array: [1, 2, 3, 4, 5]
15 },
16 aa: 'a',
17 ab: 'b',
18 }
19 };
20
21 1 const runTestCases = testRunner({context});
22
23 1 it('returns requested prop in correct object type', function (done) {
24 runTestCases([
25 {
26 input: `{{#with (getObject "a.b.c" obj)}}{{c}}{{/with}}`,
27 output: 'd',
28 },
29 {
30 input: `{{getObject "a.array[2]" obj}}`,
31 output: `3`,
32 }
33 ], done);
34 });
35
36 1 it('does not access prototype props', function (done) {
37 context.obj.__proto__ = {x: 'yz'};
38 1 runTestCases([
39 {
40 input: `{{#with (getObject "x" obj)}}{{x}}{{/with}}`,
41 output: ``,
42 },
43 ], done);
44 });
45
46 1 it('accepts SafeString paths', (done) => {
47 runTestCases([
48 {
49 input: `{{get 'aa' (getObject (concat 'a' 'a') obj)}}`,
50 output: `a`,
51 },
52 {
53 input: `{{get 'ab' (getObject (concat 'a' 'b') obj)}}`,
54 output: `b`,
55 }
56 ], done);
57 });
58 });

spec/helpers/getShortMonth.js

100%
60
60
0
Line Lint Hits Source
1 1 const Lab = require('lab'),
2 lab = exports.lab = Lab.script(),
3 describe = lab.experiment,
4 it = lab.it,
5 testRunner = require('../spec-helpers').testRunner;
6
7 1 describe('getShortMonth helper', function() {
8
9 const runTestCases = testRunner({});
10
11 1 it('should render an object properly', function(done) {
12 runTestCases([
13 {
14 input: '{{getShortMonth 1}}',
15 output: 'Jan',
16 },
17 {
18 input: '{{getShortMonth 2}}',
19 output: 'Feb',
20 },
21 {
22 input: '{{getShortMonth 3}}',
23 output: 'Mar',
24 },
25 {
26 input: '{{getShortMonth 4}}',
27 output: 'Apr',
28 },
29 {
30 input: '{{getShortMonth 5}}',
31 output: 'May',
32 },
33 {
34 input: '{{getShortMonth 6}}',
35 output: 'Jun',
36 },
37 {
38 input: '{{getShortMonth 7}}',
39 output: 'Jul',
40 },
41 {
42 input: '{{getShortMonth 8}}',
43 output: 'Aug',
44 },
45 {
46 input: '{{getShortMonth 9}}',
47 output: 'Sep',
48 },
49 {
50 input: '{{getShortMonth 10}}',
51 output: 'Oct',
52 },
53 {
54 input: '{{getShortMonth 11}}',
55 output: 'Nov',
56 },
57 {
58 input: '{{getShortMonth 12}}',
59 output: 'Dec',
60 },
61 ], done);
62 });
63 });
64

spec/helpers/helperMissing.js

100%
15
15
0
Line Lint Hits Source
1 1 const Code = require('code'),
2 Lab = require('lab'),
3 lab = exports.lab = Lab.script(),
4 describe = lab.experiment,
5 expect = Code.expect,
6 it = lab.it,
7 renderString = require('../spec-helpers').renderString;
8
9 1 describe('helperMissing', function() {
10 it('should return empty string if the helper is missing', function(done) {
11 renderString('{{thisHelperDoesNotExist}}', {}).then(result => {
12 expect(result).to.be.equal('');
13 1 done();
14 });
15 });
16 });
17

spec/helpers/if.js

100%
414
414
0
Line Lint Hits Source
1 1 const Lab = require('lab'),
2 lab = exports.lab = Lab.script(),
3 describe = lab.experiment,
4 it = lab.it,
5 specHelpers = require('../spec-helpers'),
6 testRunner = specHelpers.testRunner,
7 renderString = specHelpers.renderString;
8
9 1 describe('if helper', () => {
10 const context = {
11 num1: 1,
12 num2: 2,
13 product: {a: 1, b: 2},
14 string: 'yolo',
15 alwaysTrue: true,
16 alwaysFalse: true,
17 big: 'big'
18 };
19
20 1 const runTestCases = testRunner({context});
21
22 1 it('should have the same behavior as the original if helper', done => {
23 runTestCases([
24 {
25 input: '{{#if 1}}{{big}}{{/if}}',
26 output: 'big',
27 },
28 {
29 input: '{{#if 1}}big{{/if}}',
30 output: 'big',
31 },
32 {
33 input: '{{#if "x"}}big{{/if}}',
34 output: 'big',
35 },
36 {
37 input: '{{#if ""}}big{{/if}}',
38 output: '',
39 },
40 {
41 input: '{{#if 0}}big{{/if}}',
42 output: '',
43 },
44 {
45 input: '{{#if ""}}big{{else}}small{{/if}}',
46 output: 'small',
47 },
48 {
49 input: '{{#if 0}}big{{else}}small{{/if}}',
50 output: 'small',
51 },
52 {
53 input: '{{#if num2}}big{{else}}small{{/if}}',
54 output: 'big',
55 },
56 {
57 input: '{{#if product}}big{{else}}small{{/if}}',
58 output: 'big',
59 },
60 {
61 input: '{{#if string}}big{{else}}small{{/if}}',
62 output: 'big',
63 },
64 ], done);
65 });
66
67 1 it('should render "big" if all conditions match', done => {
68 runTestCases([
69 {
70 input: '{{#if "1" "==" num1}}big{{/if}}',
71 output: 'big',
72 },
73 {
74 input: '{{#if 1 "===" num1}}big{{/if}}',
75 output: 'big',
76 },
77 {
78 input: '{{#if 2 "!==" num1}}big{{/if}}',
79 output: 'big',
80 },
81 {
82 input: '{{#if num2 "!=" num1}}big{{/if}}',
83 output: 'big',
84 },
85 {
86 input: '{{#if num2 ">" num1}}big{{/if}}',
87 output: 'big',
88 },
89 {
90 input: '{{#if num1 "<" num2}}big{{/if}}',
91 output: 'big',
92 },
93 {
94 input: '{{#if num2 ">=" num1}}big{{/if}}',
95 output: 'big',
96 },
97 {
98 input: '{{#if num1 "<=" num2}}big{{/if}}',
99 output: 'big',
100 },
101 {
102 input: '{{#if product "typeof" "object"}}big{{/if}}',
103 output: 'big',
104 },
105 {
106 input: '{{#if "2" "gtnum" "1"}}big{{/if}}',
107 output: 'big',
108 },
109 {
110 input: '{{#if string "typeof" "string"}}big{{/if}}',
111 output: 'big',
112 },
113 ], done);
114 });
115
116 1 it('should render empty for all cases', done => {
117 runTestCases([
118 {
119 input: '{{#if "2" "==" num1}}big{{/if}}',
120 output: '',
121 },
122 {
123 input: '{{#if 2 "===" num1}}big{{/if}}',
124 output: '',
125 },
126 {
127 input: '{{#if 1 "!==" num1}}big{{/if}}',
128 output: '',
129 },
130 {
131 input: '{{#if num2 "!=" 2}}big{{/if}}',
132 output: '',
133 },
134 {
135 input: '{{#if num1 ">" 20}}big{{/if}}',
136 output: '',
137 },
138 {
139 input: '{{#if 4 "<" num2}}big{{/if}}',
140 output: '',
141 },
142 {
143 input: '{{#if num1 ">=" 40}}big{{/if}}',
144 output: '',
145 },
146 {
147 input: '{{#if num2 "<=" num1}}big{{/if}}',
148 output: '',
149 },
150 {
151 input: '{{#if "1" "gtnum" "2"}}big{{/if}}',
152 output: '',
153 },
154 {
155 input: '{{#if "1" "gtnum" "1"}}big{{/if}}',
156 output: '',
157 },
158 {
159 input: '{{#if product "typeof" "string"}}big{{/if}}',
160 output: '',
161 },
162 {
163 input: '{{#if string "typeof" "object"}}big{{/if}}',
164 output: '',
165 },
166 ], done);
167 });
168
169 1 it('should ignore additional arguments and process only the first three arguments', done => {
170 runTestCases([
171 {
172 input: '{{#if "1" "<" "2" ">" "3"}}big{{/if}}',
173 output: 'big'
174 },
175 {
176 input: '{{#if "1" "<" "-1" ">" "3"}}big{{/if}}',
177 output: ''
178 },
179 {
180 input: '{{#if "1" "<" "1" ">" "3"}}big{{/if}}',
181 output: ''
182 }
183 ], done)
184 });
185
186 1 it('should throw an exception when non string value sent to gtnum', function (done) {
187 renderString('{{#if num1 "gtnum" "2"}}big{{/if}}').catch(e => {
188 renderString('{{#if "2" "gtnum" num2}}big{{/if}}').catch(e => {
189 renderString('{{#if num1 "gtnum" num2}}big{{/if}}').catch(e => {
190 done();
191 });
192 });
193 });
194 });
195
196 1 it('should throw an exception when NaN value sent to gtnum', function (done) {
197 renderString('{{#if "aaaa" "gtnum" "2"}}big{{/if}}').catch(e => {
198 renderString('{{#if "2" "gtnum" "bbbb"}}big{{/if}}').catch(e => {
199 renderString('{{#if "aaaa" "gtnum" "bbbb"}}big{{/if}}').catch(e => {
200 done();
201 });
202 });
203 });
204 });
205
206
207 1 it('should render "big" if all ifs match', done => {
208 const context = {
209 num1: 1,
210 num2: 2,
211 product: {a: 1, b: 2},
212 string: 'yolo'
213 };
214
215 1 runTestCases([
216 {
217 input: '{{#if "1" num1 operator="=="}}big{{/if}}',
218 output: 'big',
219 context: context,
220 },
221 {
222 input: '{{#if 1 num1 operator="==="}}big{{/if}}',
223 output: 'big',
224 context: context,
225 },
226 {
227 input: '{{#if 2 num1 operator="!=="}}big{{/if}}',
228 output: 'big',
229 context: context,
230 },
231 {
232 input: '{{#if num2 num1 operator="!="}}big{{/if}}',
233 output: 'big',
234 context: context,
235 },
236 {
237 input: '{{#if num2 num1 operator=">"}}big{{/if}}',
238 output: 'big',
239 context: context,
240 },
241 {
242 input: '{{#if num1 num2 operator="<"}}big{{/if}}',
243 output: 'big',
244 context: context,
245 },
246 {
247 input: '{{#if num2 num1 operator=">="}}big{{/if}}',
248 output: 'big',
249 context: context,
250 },
251 {
252 input: '{{#if num1 num2 operator="<="}}big{{/if}}',
253 output: 'big',
254 context: context,
255 },
256 {
257 input: '{{#if product "object" operator="typeof"}}big{{/if}}',
258 output: 'big',
259 context: context,
260 },
261 {
262 input: '{{#if string "string" operator="typeof"}}big{{/if}}',
263 output: 'big',
264 context: context,
265 },
266 ], done);
267 });
268
269 1 it('should render empty for all cases', done => {
270 const context = {
271 num1: 1,
272 num2: 2,
273 product: {a: 1, b: 2},
274 string: 'yolo',
275 emptyArray: [],
276 emptyObject: {}
277 };
278
279 1 runTestCases([
280 {
281 input: '{{#if emptyObject}}big{{/if}}',
282 output: '',
283 context: context,
284 },
285 {
286 input: '{{#if emptyArray}}big{{/if}}',
287 output: '',
288 context: context,
289 },
290 {
291 input: '{{#if emptyArray.length}}big{{/if}}',
292 output: '',
293 context: context,
294 },
295 {
296 input: '{{#if "2" num1 operator="=="}}big{{/if}}',
297 output: '',
298 context: context,
299 },
300 {
301 input: '{{#if 2 num1 operator="==="}}big{{/if}}',
302 output: '',
303 context: context,
304 },
305 {
306 input: '{{#if 1 num1 operator="!=="}}big{{/if}}',
307 output: '',
308 context: context,
309 },
310 {
311 input: '{{#if num2 2 operator="!="}}big{{/if}}',
312 output: '',
313 context: context,
314 },
315 {
316 input: '{{#if num1 20 operator=">"}}big{{/if}}',
317 output: '',
318 context: context,
319 },
320 {
321 input: '{{#if 4 num2 operator="<"}}big{{/if}}',
322 output: '',
323 context: context,
324 },
325 {
326 input: '{{#if num1 40 operator=">="}}big{{/if}}',
327 output: '',
328 context: context,
329 },
330 {
331 input: '{{#if num2 num1 operator="<="}}big{{/if}}',
332 output: '',
333 context: context,
334 },
335 {
336 input: '{{#if product "string" operator="typeof"}}big{{/if}}',
337 output: '',
338 context: context,
339 },
340 {
341 input: '{{#if string "object" operator="typeof"}}big{{/if}}',
342 output: '',
343 context: context,
344 },
345 ], done);
346 });
347
348 1 it('should work as a non-block helper when used as a subexpression', done => {
349 runTestCases([
350 {
351 input: '{{#if (if num1 "!==" num2)}}{{big}}{{/if}}',
352 output: 'big',
353 },
354 {
355 input: '{{#all (if num1 "!==" num2) "1" true}}big{{/all}}',
356 output: 'big',
357 },
358 ], done);
359 });
360 });
361
362 1 describe('unless helper', () => {
363 const context = {
364 num1: 1,
365 num2: 2,
366 product: {a: 1, b: 2},
367 alwaysTrue: true,
368 alwaysFalse: false,
369 notEmptyArray: [ 1, 2 , 3 ],
370 emptyArray: [],
371 };
372
373 1 const runTestCases = testRunner({context});
374
375 1 it('should print hello', done => {
376 runTestCases([
377 {
378 input: '{{#unless num1 "===" num2}}hello{{/unless}}',
379 output: 'hello',
380 },
381 {
382 input: '{{#unless alwaysFalse}}hello{{/unless}}',
383 output: 'hello',
384 },
385 {
386 input: '{{#unless does_not_exist}}hello{{/unless}}',
387 output: 'hello',
388 },
389 ], done);
390 });
391
392 1 it('should print empty', done => {
393 runTestCases([
394 {
395 input: '{{#unless num1 "===" num1}}hello{{/unless}}',
396 output: '',
397 },
398 {
399 input: '{{#unless alwaysTrue}}hello{{/unless}}',
400 output: '',
401 },
402 {
403 input: '{{#unless product}}hello{{/unless}}',
404 output: '',
405 },
406 ], done);
407 });
408
409 1 it('should work with arrays', done => {
410 runTestCases([
411 {
412 input: '{{#unless emptyArray}}foo{{else}}bar{{/unless}}',
413 output: 'foo',
414 },
415 {
416 input: '{{#unless notEmptyArray}}foo{{else}}bar{{/unless}}',
417 output: 'bar',
418 },
419 ], done);
420 });
421
422 1 it('should work as a non-block helper when used as a subexpression', done => {
423 runTestCases([
424 {
425 input: '{{#if (unless num1 "===" num2)}}big{{/if}}',
426 output: 'big',
427 },
428 {
429 input: '{{#all (unless num1 "===" num2) "1" true}}big{{/all}}',
430 output: 'big',
431 },
432 ], done);
433 });
434 })
435

spec/helpers/incrementVar.js

100%
62
62
0
Line Lint Hits Source
1 1 const Lab = require('lab'),
2 lab = exports.lab = Lab.script(),
3 describe = lab.experiment,
4 it = lab.it,
5 specHelpers = require('../spec-helpers'),
6 testRunner = specHelpers.testRunner,
7 renderString = specHelpers.renderString;
8
9
10 1 describe('incrementVar helper', function() {
11 const context = {
12 value1: 12
13 };
14
15 1 const runTestCases = testRunner({context});
16
17 1 it('should throw an exception if the incrementVar key is not a string', function (done) {
18 renderString('{{incrementVar 1}}').catch(e => {
19 done();
20 });
21 });
22
23 1 it('should correctly increment', function(done) {
24 runTestCases([
25 {
26 input: "{{incrementVar 'data1'}}{{getVar 'data1'}} {{incrementVar 'data1'}}{{getVar 'data1'}} {{incrementVar 'data1'}}{{getVar 'data1'}}",
27 output: '00 11 22',
28 },
29 ], done);
30 });
31
32 1 it('should correctly increment an existing variable', function(done) {
33 runTestCases([
34 {
35 input: "{{assignVar 'data1' value1}}{{getVar 'data1'}} {{incrementVar 'data1'}}",
36 output: '12 13',
37 },
38 {
39 input: "{{assignVar 'data1' 12}}{{getVar 'data1'}} {{incrementVar 'data1'}}",
40 output: '12 13',
41 },
42 {
43 input: "{{assignVar 'data1' -12}}{{getVar 'data1'}} {{incrementVar 'data1'}}",
44 output: '-12 -11',
45 },
46 ], done);
47 });
48
49 1 it('should correctly overwrite an existing non-integer variable', function(done) {
50 runTestCases([
51 {
52 input: "{{assignVar 'data1' 'a'}}{{getVar 'data1'}} {{incrementVar 'data1'}}",
53 output: 'a 0',
54 },
55 ], done);
56 });
57
58
59
60 1 it('should correctly return data accession object proto/constructor', function(done) {
61 runTestCases([
62 {
63 input: "{{incrementVar '__proto__'}}",
64 output: '0',
65 },
66 {
67 input: "{{incrementVar 'constructor'}}",
68 output: '0',
69 },
70 ], done);
71 });
72 });
73

spec/helpers/inject.js

100%
20
20
0
Line Lint Hits Source
1 1 const Lab = require('lab'),
2 lab = exports.lab = Lab.script(),
3 describe = lab.experiment,
4 it = lab.it,
5 testRunner = require('../spec-helpers').testRunner;
6
7 1 describe('inject helper', function() {
8 const context = {
9 value1: "Big",
10 value2: "Commerce",
11 };
12
13 1 const runTestCases = testRunner({context});
14
15 1 it('should inject variables', function(done) {
16 runTestCases([
17 {
18 input: "{{inject 'data1' value1}}{{inject 'data2' value2}}{{jsContext}}",
19 output: '"{\\"data1\\":\\"Big\\",\\"data2\\":\\"Commerce\\"}"',
20 },
21 ], done);
22 });
23 });
24

spec/helpers/join.js

100%
48
48
0
Line Lint Hits Source
1 1 const Lab = require('lab'),
2 lab = exports.lab = Lab.script(),
3 describe = lab.experiment,
4 it = lab.it,
5 testRunner = require('../spec-helpers').testRunner;
6
7 1 describe('join helper', function() {
8 const context = {
9 list: ['Mario', 'Chris', 'Mick', 'Hau', 'Cody'],
10 notArray: null,
11 };
12
13 1 const runTestCases = testRunner({context});
14
15 1 it('should print a list of names', function(done) {
16 runTestCases([
17 {
18 input: '{{join list " "}}',
19 output: 'Mario Chris Mick Hau Cody',
20 },
21 {
22 input: '{{join list ", "}}',
23 output: 'Mario, Chris, Mick, Hau, Cody',
24 },
25 ], done);
26 });
27
28 1 it('should print a list of names and limit to 3', function(done) {
29 runTestCases([
30 {
31 input: '{{join list " " limit=3}}',
32 output: 'Mario Chris Mick',
33 },
34 ], done);
35 });
36
37 1 it('should print a list of names and use "and" for the last name', function(done) {
38 runTestCases([
39 {
40 input: '{{join list ", " lastSeparator=" and "}}',
41 output: 'Mario, Chris, Mick, Hau and Cody',
42 },
43 ], done);
44 });
45
46 1 it('should print a list of names and limit to 3 and use "and" for the last name', function(done) {
47 runTestCases([
48 {
49 input: '{{join list ", " limit=3 lastSeparator=" and "}}',
50 output: 'Mario, Chris and Mick',
51 },
52 ], done);
53 });
54 });
55

spec/helpers/json.js

100%
39
39
0
Line Lint Hits Source
1 1 const Lab = require('lab'),
2 lab = exports.lab = Lab.script(),
3 describe = lab.experiment,
4 it = lab.it,
5 testRunner = require('../spec-helpers').testRunner;
6
7 1 describe('json helper', function() {
8 const urlData_2_qs = 'https://cdn.example.com/path/to/{:size}/image.png?c=2&imbypass=on';
9 1 const context = {
10 image_with_2_qs: {
11 data: urlData_2_qs
12 },
13 object: { a: 1, b: "hello" }
14 };
15
16 1 const runTestCases = testRunner({context});
17
18 1 it('should render object to json format', function(done) {
19 runTestCases([
20 {
21 input: '{{{json object}}}',
22 output: '{"a":1,"b":"hello"}',
23 },
24 ], done);
25 });
26 1 it('should work together with getImage', function(done) {
27 runTestCases([
28 {
29 input: '{{{json (getImage image_with_2_qs)}}}',
30 output: '"https://cdn.example.com/path/to/original/image.png?c=2&imbypass=on"',
31 },
32 ], done);
33 });
34
35 1 it('should work together with concat', function(done) {
36 runTestCases([
37 {
38 input: '{{{json (concat \'Hello \' \'World\')}}}',
39 output: '"Hello World"',
40 },
41 ], done);
42 });
43 });
44

spec/helpers/jsonParseSafe.js

100%
81
81
0
Line Lint Hits Source
1 1 const { describe, it } = exports.lab = require('lab').script();
2
3 1 const { testRunner } = require('../spec-helpers');
4
5 1 describe('JSONParseSafe helper', () => {
6 const context = {
7 jsonString: '{"name": "John"}',
8 string: 'John Doe',
9 };
10 1 const runTestCases = testRunner({context});
11
12 1 it('#Block: should execute the main instruction', done => {
13 runTestCases([
14 {
15 input: '{{#JSONparseSafe jsonString}}{{name}}{{/JSONparseSafe}}',
16 output: 'John',
17 },
18 ], done);
19 });
20
21 1 it('#Block: should skip the main instruction if variable is non-json', done => {
22 runTestCases([
23 {
24 input: '{{#JSONparseSafe string}}{{name}}{{/JSONparseSafe}}',
25 output: '',
26 },
27 ], done);
28 });
29
30 1 it('#Block: should execute the else instruction if variable is non-json', done => {
31 runTestCases([
32 {
33 input: '{{#JSONparseSafe string}}{{name}}{{else}}{{string}}{{/JSONparseSafe}}',
34 output: 'John Doe',
35 },
36 ], done);
37 });
38
39 // Variation of the Block Helper test to convert to an Inline Call.
40 1 it('Inline: Output should match the same as `main test`', done => {
41 runTestCases([
42 {
43 input: '{{#with (JSONparseSafe jsonString)}}{{this.name}}{{/with}}',
44 output: 'John',
45 },
46 ], done);
47 });
48
49 1 it('Inline: Using JSON.stringify helper, and a empty object; parse the empty object and output it', done => {
50 runTestCases([
51 {
52 input: '{{#with (JSONparseSafe "{}")}}{{json this}}{{/with}}',
53 output: '{}',
54 },
55 ], done);
56 });
57
58
59 1 it('Inline: Using JSON.stringify helper, and a invalid / malformed object; parse the invalid object and output it, output is a Javascript `undefined` object', done => {
60 runTestCases([
61 {
62 input: '{{#with (JSONparseSafe "{")}}Undefined response due to malformed input{{else}}{{/with}}',
63 output: '',
64 },
65 ], done);
66 });
67
68 1 it('Inline: Stringifed example - with values', done => {
69 runTestCases([
70 {
71 input: `{{json (JSONparseSafe '{"test": "value"}')}}`,
72 output: "{&quot;test&quot;:&quot;value&quot;}",
73 },
74 ], done);
75 });
76
77
78 1 it('Inline: Stringifed example - without values', done => {
79 runTestCases([
80 {
81 input: `{{json (JSONparseSafe '{}')}}`,
82 output: '{}',
83 },
84 ], done);
85 });
86
87 1 it('Inline: Stringifed example - malformed input', done => {
88 runTestCases([
89 {
90 input: `{{json (JSONparseSafe '{')}}`,
91 output: '',
92 },
93 ], done);
94 });
95 });
96

spec/helpers/lang.js

100%
54
54
0
Line Lint Hits Source
1 'use strict';
2
3 1 const Lab = require('lab');
4 1 const lab = exports.lab = Lab.script();
5 1 const beforeEach = lab.beforeEach;
6 1 const describe = lab.experiment;
7 1 const it = lab.it;
8
9 1 const specHelpers = require('../spec-helpers');
10 1 const buildRenderer = specHelpers.buildRenderer;
11 1 const testRunner = specHelpers.testRunner;
12
13 1 describe('lang helper', () => {
14 let context, renderer, runTestCases;
15
16 1 beforeEach(done => {
17 context = {
18 name: 'BigCommerce',
19 test: {},
20 };
21
22 3 renderer = buildRenderer();
23 3 renderer.setTranslator({
24 getLocale: () => 'en',
25 translate: (key, params) => `Powered By ${params.name}`,
26 });
27
28 3 runTestCases = testRunner({renderer, context});
29
30 3 done();
31 });
32
33 1 it('should translate the key with attributes', done => {
34 runTestCases([
35 {
36 input: '{{lang "powered_by" name=name}}',
37 output: 'Powered By BigCommerce',
38 },
39 ], done);
40 });
41
42 1 it('should return an empty string if translator is undefined', done => {
43 renderer.setTranslator(null);
44
45 1 runTestCases([
46 {
47 input: '{{lang "powered_by" name=name}}',
48 output: '',
49 },
50 ], done);
51 });
52
53 1 it('should return an empty string if translastor returns an object', done => {
54 renderer.setTranslator({
55 getLocale: () => 'en',
56 translate: (key, params) => params.test,
57 });
58
59 1 runTestCases([
60 {
61 input: '{{lang "some_key" name=test}}',
62 output: '',
63 },
64 ], done);
65 });
66 });
67

spec/helpers/langJson.js

100%
38
38
0
Line Lint Hits Source
1 'use strict';
2
3 1 const Lab = require('lab');
4 1 const lab = exports.lab = Lab.script();
5 1 const beforeEach = lab.beforeEach;
6 1 const describe = lab.experiment;
7 1 const it = lab.it;
8
9 1 const specHelpers = require('../spec-helpers');
10 1 const buildRenderer = specHelpers.buildRenderer;
11 1 const testRunner = specHelpers.testRunner;
12
13 1 describe('langJson helper', () => {
14 let renderer, runTestCases;
15
16 1 beforeEach(done => {
17 renderer = buildRenderer();
18 2 renderer.setTranslator({
19 getLocale: () => 'en',
20 getLanguage: () => ({ locale: 'en' }),
21 });
22
23 2 runTestCases = testRunner({renderer});
24
25 2 done();
26 });
27
28 1 it('should return translation as JSON string if translator is defined', done => {
29 runTestCases([
30 {
31 input: '{{{langJson}}}',
32 output: JSON.stringify(renderer.getTranslator().getLanguage()),
33 },
34 ], done);
35 });
36
37 1 it('should return an empty object as JSON string if translator is not defined', done => {
38 renderer.setTranslator(null);
39
40 1 runTestCases([
41 {
42 input: '{{{langJson}}}',
43 output: '{}',
44 },
45 ], done);
46 });
47 });
48

spec/helpers/limit.js

100%
26
26
0
Line Lint Hits Source
1 1 const Lab = require('lab'),
2 lab = exports.lab = Lab.script(),
3 describe = lab.experiment,
4 it = lab.it,
5 testRunner = require('../spec-helpers').testRunner;
6
7 1 describe('limit helper', function() {
8 const runTestCases = testRunner({});
9
10 1 it('should limit an array properly', function(done) {
11 runTestCases([
12 {
13 input: '{{#each (limit var 4)}}{{this}} {{/each}}',
14 output: '1 2 3 4 ',
15 context: {var: [1,2,3,4,5,6,7,8]},
16 },
17 ], done);
18 });
19
20 1 it('should limit an string properly', function(done) {
21 runTestCases([
22 {
23 input: '{{limit var 10}}',
24 output: 'This is lo',
25 context: {var: "This is longer than the chosen limit"},
26 },
27 ], done);
28 });
29 });
30

spec/helpers/moment.js

100%
93
93
0
Line Lint Hits Source
1 1 const Sinon = require('sinon');
2 1 const Lab = require('lab'),
3 lab = exports.lab = Lab.script(),
4 describe = lab.experiment,
5 it = lab.it,
6 beforeEach = lab.beforeEach,
7 afterEach = lab.afterEach,
8 testRunner = require('../spec-helpers').testRunner;
9 1 const Code = require('code'),
10 expect = Code.expect;
11 1 const moment = require('moment');
12
13 1 describe('moment helper', function () {
14 const runTestCases = testRunner({});
15
16 1 it('renders the date in the format specified', function (done) {
17 const now = new Date();
18 1 runTestCases([
19 {
20 input: `{{moment "1 year ago" "YYYY"}}`,
21 output: `${now.getFullYear() - 1}`,
22 },
23 {
24 input: `{{moment "2 days ago" "YYYY"}}`,
25 output: `${now.getFullYear()}`,
26 },
27 {
28 input: `{{moment '2022-06-29' 'DD/MM/YYYY'}}`,
29 output: '29/06/2022',
30 },
31 {
32 input: `{{moment '2022-06-30' 'DD/MM/YYYY'}}`,
33 output: '30/06/2022',
34 },
35 {
36 input: `{{moment '2022-07-29' 'DD/MM/YYYY'}}`,
37 output: '29/07/2022',
38 },
39 {
40 input: `{{moment '2022-07-30' 'DD/MM/YYYY'}}`,
41 output: '30/07/2022',
42 }
43 ], done);
44 });
45
46 1 it('is backwards compatible with helper-date 0.2.3 null-argument behavior', (done) => {
47 runTestCases([
48 {
49 input: `{{moment format="YYYY"}}`,
50 output: moment().format('YYYY'),
51 },
52 {
53 input: `{{moment null null}}`,
54 output: moment().format('MMMM DD, YYYY'),
55 },
56 {
57 input: `{{moment undefined undefined}}`,
58 output: moment().format('MMMM DD, YYYY'),
59 },
60 {
61 input: `{{moment "now" format="YYYY"}}`,
62 output: `Invalid date`,
63 }
64 ], done);
65 });
66
67 1 it('permits use of moment.js functions', (done) => {
68 runTestCases([
69 {
70 input: `{{moment "2022-01-01" isAfter="1999-12-31"}}`,
71 output: `true`,
72 }
73 ], done);
74 });
75
76 1 describe('when amount is provided', () => {
77 let consoleWarnCopy = console.warn;
78
79 1 beforeEach(done => {
80 console.warn = Sinon.fake();
81 1 done();
82 });
83
84 1 afterEach(done => {
85 console.warn = consoleWarnCopy;
86 1 done();
87 });
88
89 1 it('runs console.warn', (done) => {
90 runTestCases([
91 {
92 input: `{{moment "2022-01-01" isAfter="1999-12-31" amount=1}}`,
93 output: `true`,
94 }
95 ], () => {
96 expect(console.warn.called).to.equal(true);
97 1 done();
98 });
99
100 });
101 });
102 });

spec/helpers/money.js

100%
84
84
0
Line Lint Hits Source
1 1 const Lab = require('lab');
2 1 const { expect } = require('code');
3 1 const lab = exports.lab = Lab.script();
4 1 const describe = lab.experiment;
5 1 const it = lab.it;
6 1 const { testRunner, renderString } = require('../spec-helpers');
7
8 1 describe('money helper', function() {
9 const context = {
10 price: 1234.56,
11 };
12 1 const siteSettings = {
13 money: {
14 currency_location: "left",
15 currency_token: "$",
16 decimal_places: 2,
17 thousands_token: ',',
18 decimal_token: '.',
19 }
20 };
21
22
23 1 it('should correctly set money value from money site settings', function(done) {
24 const runTestCases = testRunner({context, siteSettings});
25 1 runTestCases([
26 {
27 input: '{{money price}}',
28 output: '$ 1,234.56',
29 },
30 {
31 input: '{{money 12.34}}',
32 output: '$ 12.34',
33 },
34 ], done);
35 });
36
37 1 it('should read money site settings from input parameters', function(done) {
38 const runTestCases = testRunner({context, siteSettings});
39 1 runTestCases([
40 {
41 input: '{{money price 1}}',
42 output: '$ 1,234.6',
43 },
44 {
45 input: '{{money price 3}}',
46 output: '$ 1,234.560',
47 },
48 {
49 input: '{{money price 2 "."}}',
50 output: '$ 1.234.56',
51 },
52 {
53 input: '{{money price 2 "," ","}}',
54 output: '$ 1,234,56',
55 },
56 ], done);
57 });
58
59 1 it('should correctly set currency position regardless of location capitalization - [STRF-10878]', function(done) {
60 const settingsWithCapitalizedLocation = {
61 money: {
62 currency_location: "Left",
63 currency_token: "$",
64 decimal_places: 2,
65 thousands_token: ',',
66 decimal_token: '.',
67 }
68 };
69
70 1 const runTestCases = testRunner({ context, siteSettings: settingsWithCapitalizedLocation });
71 1 runTestCases([
72 {
73 input: '{{money price}}',
74 output: '$ 1,234.56',
75 },
76 ], done);
77 });
78
79 1 it('should throw an exception if the price value parameter has an invalid type', function(done) {
80 renderString('{{money "hello"}}').catch(err => {
81 expect(err.message.startsWith("money helper accepts only Number's as first parameter")).to.be.true();
82 1 done();
83 });
84 });
85
86 1 it('should throw an exception if the decimal places parameter has an invalid type', function(done) {
87 renderString('{{money 1.2 "hello"}}').catch(err => {
88 expect(err.message.startsWith("money helper accepts only Number's for decimal places")).to.be.true();
89 1 done();
90 });
91 });
92 });
93

spec/helpers/multiConcat.js

100%
50
50
0
Line Lint Hits Source
1 1 const Lab = require('lab'),
2 lab = exports.lab = Lab.script(),
3 describe = lab.experiment,
4 it = lab.it,
5 testRunner = require('../spec-helpers').testRunner;
6
7
8 1 describe('multiConcat helper', function() {
9 const context = {
10 string1: "First",
11 string2: "Second",
12 string3: "Third",
13 string4: "Fourth"
14 };
15
16 1 const runTestCases = testRunner({context});
17
18 1 it('should concatenate all strings by default', function(done) {
19 runTestCases([
20 {
21 input: '{{multiConcat string1 string2 string3}}',
22 output: 'FirstSecondThird',
23 },
24 {
25 input: '{{multiConcat string1 string2 string3 string4}}',
26 output: 'FirstSecondThirdFourth',
27 },
28 {
29 input: '{{multiConcat string1 string2}}',
30 output: 'FirstSecond',
31 }
32 ], done);
33 });
34
35 1 it('should accept string, number, boolean, empty', function(done) {
36 runTestCases([
37 {
38 input: '{{multiConcat "First" 2}}',
39 output: 'First2',
40 },
41 {
42 input: '{{multiConcat string1 3 "" "4" true}}',
43 output: 'First34true',
44 },
45 {
46 input: '{{multiConcat string1 3 false "" "4" true}}',
47 output: 'First3false4true',
48 },
49 {
50 input: '{{multiConcat string1 ""}}',
51 output: 'First',
52 }
53 ], done);
54 });
55 });
56

spec/helpers/nl2br.js

100%
19
19
0
Line Lint Hits Source
1 1 const Lab = require('lab'),
2 lab = exports.lab = Lab.script(),
3 describe = lab.experiment,
4 it = lab.it,
5 testRunner = require('../spec-helpers').testRunner;
6
7 1 describe('nl2br helper', function() {
8 const context = {
9 text: "Hello\nmy\nname\nis\nJack"
10 };
11
12 1 const runTestCases = testRunner({context});
13
14 1 it('should convert new lines to <br> tags', function(done) {
15 runTestCases([
16 {
17 input: '{{nl2br text}}',
18 output: 'Hello<br>\nmy<br>\nname<br>\nis<br>\nJack',
19 },
20 ], done);
21 });
22 });
23

spec/helpers/nonce.js

100%
35
35
0
Line Lint Hits Source
1 1 const Lab = require('lab');
2 1 const lab = exports.lab = Lab.script();
3 1 const { describe, it} = lab;
4 1 const {testRunner, buildRenderer, randomString} = require('../spec-helpers');
5
6 1 describe('nonce helper', function () {
7 const context = {}
8
9 1 it('should render a nonce in quotes with the correct value from request params', function (done) {
10 const requestParams = {
11 security: {
12 nonce: randomString()
13 },
14 }
15 1 const renderer = buildRenderer({}, {}, {}, null, requestParams);
16 1 const runTestCases = testRunner({context, renderer});
17 1 runTestCases([
18 {
19 input: '{{nonce}}',
20 output: requestParams.security.nonce,
21 },
22 ], done);
23 });
24
25 1 it('should not render a nonce since request param is empty', function (done) {
26 const requestParams = {
27 security: {},
28 }
29 1 const renderer = buildRenderer({}, {}, {}, null, requestParams);
30 1 const runTestCases = testRunner({context, renderer});
31 1 runTestCases([
32 {
33 input: '{{nonce}}',
34 output: '',
35 },
36 ], done);
37 });
38 });

spec/helpers/occurrences.js

100%
79
79
0
Line Lint Hits Source
1 1 const Lab = require('lab'),
2 lab = exports.lab = Lab.script(),
3 describe = lab.experiment,
4 it = lab.it,
5 testRunner = require('../spec-helpers').testRunner;
6
7 1 describe('occurrences helper', function() {
8 const context = {
9 string: "asdf asdf2 xxx yyy",
10 number: 1,
11 object: {
12 asdf: 1,
13 asdf2: 2,
14 xxx: 3,
15 yyy: 4,
16 },
17 array: ['asdf', 'asdf2', 'xxx', 'yyy']
18 };
19
20 1 const runTestCases = testRunner({context});
21
22 1 it('should count number of occurrences of substring', function(done) {
23 runTestCases([
24 {
25 input: "{{occurrences string 'asdf'}}",
26 output: '2',
27 },
28 {
29 input: "{{occurrences string 'xxx'}}",
30 output: '1',
31 },
32 {
33 input: "{{occurrences string 'x'}}",
34 output: '3',
35 },
36 {
37 input: "{{occurrences string 'zzz'}}",
38 output: '0',
39 },
40 ], done);
41 });
42
43 1 it('should ignore non-strings', function(done) {
44 runTestCases([
45 {
46 input: "{{occurrences object 'asdf'}}",
47 output: '0',
48 },
49 {
50 input: "{{occurrences array 'asdf'}}",
51 output: '0',
52 },
53 {
54 input: "{{occurrences number '1'}}",
55 output: '0',
56 },
57 {
58 input: "{{occurrences string object}}",
59 output: '0',
60 },
61 {
62 input: "{{occurrences string array}}",
63 output: '0',
64 },
65 {
66 input: "{{occurrences '1' number}}",
67 output: '0',
68 },
69 ], done);
70 });
71
72 1 it('should ignore empty strings', function(done) {
73 runTestCases([
74 {
75 input: "{{occurrences '' 'asdf'}}",
76 output: '0',
77 },
78 {
79 input: "{{occurrences string ''}}",
80 output: '0',
81 },
82 ], done);
83 });
84 });
85

spec/helpers/option.js

100%
37
37
0
Line Lint Hits Source
1 1 const Lab = require('lab'),
2 lab = exports.lab = Lab.script(),
3 describe = lab.experiment,
4 it = lab.it,
5 testRunner = require('../spec-helpers').testRunner;
6
7 1 describe('option helper', function () {
8 const context = {
9 array: [1, 2, 3, 4, 5],
10 options: { a: { b: { c: 'd' } } }
11 };
12
13 1 const runTestCases = testRunner({context});
14
15 1 it('returns the nested prop of this.options', function (done) {
16 runTestCases([
17 {
18 input: `{{option "a.b.c"}}`,
19 output: 'd',
20 },
21 ], done);
22 });
23
24 1 it('does not access prototype props', function (done) {
25 context.options.__proto__ = {x: 'yz'};
26 1 runTestCases([
27 {
28 input: `{{option "x"}}`,
29 output: ``,
30 },
31 ], done);
32 });
33
34 1 it('accepts SafeString paths', (done) => {
35 runTestCases([
36 {
37 input: `{{option (concat 'a.b.' 'c')}}`,
38 output: `d`,
39 },
40 ], done);
41 });
42 });

spec/helpers/or.js

100%
53
53
0
Line Lint Hits Source
1 1 const Lab = require('lab'),
2 lab = exports.lab = Lab.script(),
3 describe = lab.experiment,
4 it = lab.it,
5 testRunner = require('../spec-helpers').testRunner;
6
7 1 describe('or helper', function() {
8 const context = {
9 num1: 1,
10 num2: 2,
11 product: {a: 1, b: 2},
12 string: 'yolo',
13 alwaysTrue: true,
14 alwaysFalse: false,
15 emptyArray: [],
16 emptyObject: {},
17 itemArray: [1,2],
18 big: 'big',
19 arrayWithObjs: [{a: 1},{b: 1},{a: 2}]
20 };
21
22 1 const runTestCases = testRunner({context});
23
24 1 it('should return "big" if at least one arg valid', function(done) {
25 runTestCases([
26 {
27 input: '{{#or arrayWithObjs string}}{{big}}{{/or}}',
28 output: 'big',
29 },
30 {
31 input: '{{#or "something test" itemArray}}{{big}}{{/or}}',
32 output: 'big',
33 },
34 {
35 input: '{{#or "this is before the other test"}}{{big}}{{/or}}',
36 output: 'big',
37 },
38 {
39 input: '{{#or alwaysFalse emptyArray string}}{{big}}{{/or}}',
40 output: 'big',
41 },
42 ], done);
43 });
44
45 1 it('should return "" when no arguments are valid', function(done) {
46 runTestCases([
47 {
48 input: '{{#or emptyArray emptyObject alwaysFalse}}{{big}}{{/or}}',
49 output: '',
50 },
51 {
52 input: '{{#or "" false}}{{big}}{{/or}}',
53 output: '',
54 },
55 ], done);
56 });
57 });
58
59

spec/helpers/pluck.js

100%
52
52
0
Line Lint Hits Source
1 1 const Lab = require('lab'),
2 lab = exports.lab = Lab.script(),
3 describe = lab.experiment,
4 it = lab.it,
5 testRunner = require('../spec-helpers').testRunner;
6
7 1 describe('pluck helper', function() {
8 const context = {
9 users: [
10 { 'user': 'barney', 'age': 36 },
11 { 'user': 'fred', 'age': 40 }
12 ],
13 null: null,
14 undefined: undefined,
15 };
16
17 1 const runTestCases = testRunner({context});
18
19 1 it('should get the values from all elements in collection', function(done) {
20 runTestCases([
21 {
22 input: '{{pluck users "age"}}',
23 output: '36,40',
24 },
25 {
26 input: '{{#each (pluck users "user")}}hello {{this}} {{/each}}',
27 output: 'hello barney hello fred ',
28 },
29 ], done);
30 });
31
32 1 it('should return undefined when accessing proto/constructor', function(done) {
33 runTestCases([
34 {
35 input: '{{pluck users "__proto__"}}',
36 output: ',',
37 },
38 {
39 input: '{{pluck users "constructor"}}',
40 output: ',',
41 },
42 ], done);
43 });
44
45 1 it('should return empty array when null is provided', function(done) {
46 runTestCases([
47 {
48 input: '{{pluck null "age"}}',
49 output: '',
50 },
51 {
52 input: '{{pluck undefined "age"}}',
53 output: '',
54 },
55 ], done);
56 });
57 });
58

spec/helpers/pre.js

100%
26
26
0
Line Lint Hits Source
1 1 const Lab = require('lab'),
2 lab = exports.lab = Lab.script(),
3 describe = lab.experiment,
4 it = lab.it,
5 testRunner = require('../spec-helpers').testRunner;
6
7 1 describe('pre helper', function() {
8 const runTestCases = testRunner({});
9
10 1 it('should render an object properly', function(done) {
11 runTestCases([
12 {
13 input: '{{{pre var}}}',
14 output: '<pre>{}</pre>',
15 context: {var: {}},
16 },
17 ], done);
18 });
19
20 1 it('should scape html entities', function(done) {
21 runTestCases([
22 {
23 input: '{{{pre var}}}',
24 output: '<pre>&quot;&lt;div&gt;&amp;\\&quot;500\\&quot;&lt;/div&gt;&quot;</pre>',
25 context: {var: "<div>&\"500\"</div>"},
26 },
27 ], done);
28 });
29 });
30

spec/helpers/region.js

100%
54
54
0
Line Lint Hits Source
1 'use strict';
2
3 1 const Lab = require('lab');
4 1 const lab = exports.lab = Lab.script();
5 1 const before = lab.before;
6 1 const describe = lab.experiment;
7 1 const it = lab.it;
8
9 1 const specHelpers = require('../spec-helpers');
10 1 const buildRenderer = specHelpers.buildRenderer;
11 1 const testRunner = specHelpers.testRunner;
12
13 1 describe('Region Helper', () => {
14 let context, renderer, runTestCases;
15
16 1 before(done => {
17 context = {
18 'banner-top': "hello world"
19 };
20
21 1 renderer = buildRenderer();
22 1 renderer.setContent(context);
23
24 1 runTestCases = testRunner({context, renderer});
25
26 1 done();
27 });
28
29 1 it('should return an empty container if using empty content context', done => {
30 runTestCases([
31 {
32 input: '{{region name="banner-bottom" translation="i18n.RegionName.TestingTranslation"}}',
33 output: '<div data-content-region="banner-bottom" data-content-region-translation="i18n.RegionName.TestingTranslation"></div>',
34 renderer: buildRenderer(),
35 },
36 ], done);
37 });
38
39 1 it('should return an empty container if no matching region on context object', done => {
40 runTestCases([
41 {
42 input: '{{region name="banner-bottom" translation="i18n.RegionName.TestingTranslation"}}',
43 output: '<div data-content-region="banner-bottom" data-content-region-translation="i18n.RegionName.TestingTranslation"></div>',
44 },
45 ], done);
46 });
47
48 1 it('should return without region translation data attribute if no translation is provided', done => {
49 runTestCases([
50 {
51 input: '{{region name="banner-bottom"}}',
52 output: '<div data-content-region="banner-bottom"></div>',
53 },
54 ], done);
55 });
56
57 1 it('should return Hello World', done => {
58 runTestCases([
59 {
60 input: '{{region name="banner-top" translation="i18n.RegionName.TestingTranslation"}}',
61 output: '<div data-content-region="banner-top" data-content-region-translation="i18n.RegionName.TestingTranslation">hello world</div>',
62 },
63 ], done);
64 });
65 });
66

spec/helpers/replace.js

100%
73
73
0
Line Lint Hits Source
1 1 const Lab = require('lab'),
2 lab = exports.lab = Lab.script(),
3 describe = lab.experiment,
4 it = lab.it,
5 testRunner = require('../spec-helpers').testRunner;
6
7 1 describe('replace helper', function() {
8 const templates = {
9 template2: 'day',
10 };
11
12 1 const context = {
13 content: "Either you run the %%var%% or the %%var%% runs you",
14 price: '$49.99',
15 facet: 'brand',
16 };
17
18 1 const runTestCases = testRunner({context, templates});
19
20 1 it('should replace all ocurrance of %%var%% with "day"', function(done) {
21 runTestCases([
22 {
23 input: "{{#replace '%%var%%' content}}{{> template2}}{{/replace}}",
24 output: 'Either you run the day or the day runs you',
25 },
26 ], done);
27 });
28
29 1 it('should handle undefined values', function(done) {
30 runTestCases([
31 {
32 input: "{{#replace '%%var%%' content}}{{> template2}}{{/replace}}",
33 output: '',
34 context: {},
35 },
36 ], done);
37 });
38
39 1 it('should replace $', function(done) {
40 runTestCases([
41 {
42 input: "{{#replace '$' price}}{{/replace}}",
43 output: '49.99',
44 },
45 {
46 input: "{{#replace '$' '$10.00'}}{{/replace}}",
47 output: '10.00',
48 },
49 {
50 input: "{{#replace '$' '$10.00'}}USD {{/replace}}",
51 output: 'USD 10.00',
52 },
53 ], done);
54 });
55
56 1 it('should work nicely with other helpers that use safestring', function(done) {
57 runTestCases([
58 {
59 input: "Replace+Concat+Hyphenated: fifth-{{#replace '&' (concat '&' (hyphenate facet)) }}{{/replace}}",
60 output: "Replace+Concat+Hyphenated: fifth-brand",
61 },
62 ], done);
63 });
64
65 1 it('should gracefully handle not strings', function(done) {
66 runTestCases([
67 {
68 input: "{{#replace something price}}{{/replace}}",
69 output: '',
70 },
71 {
72 input: "{{#replace $ '$10.00'}}{{/replace}}",
73 output: '',
74 },
75 {
76 input: "{{#replace foo bar}}{{/replace}}",
77 output: '',
78 },
79 ], done);
80 });
81 });
82

spec/helpers/resourceHints.js

100%
43
43
0
Line Lint Hits Source
1 1 const Lab = require('lab'),
2 lab = exports.lab = Lab.script(),
3 describe = lab.experiment,
4 it = lab.it,
5 {testRunner, buildRenderer} = require('../spec-helpers');
6 1 const {expect} = require("code");
7 1 const {
8 resourceHintAllowedCors,
9 resourceHintAllowedStates,
10 resourceHintAllowedTypes
11 } = require('../../helpers/lib/resourceHints');
12
13 1 describe('resourceHints', function () {
14 it('should return the expected resource links', function (done) {
15 const themeSettings = {
16 'test1-font': 'Google_Open+Sans',
17 'test2-font': 'Google_Open+Sans_400italic',
18 'test3-font': 'Google_Open+Sans_700',
19 'test4-font': 'Google_Karla_700',
20 'test5-font': 'Google_Lora_400_sans',
21 'test6-font': 'Google_Volkron',
22 'test7-font': 'Google_Droid_400,700',
23 'test8-font': 'Google_Crimson+Text_400,700_sans',
24 'random-property': 'not a font'
25 };
26
27 1 const renderer = buildRenderer({}, themeSettings);
28 1 const runTestCases = testRunner({renderer});
29
30 1 runTestCases([
31 {
32 input: '{{resourceHints}}',
33 output: '<link rel="dns-prefetch preconnect" href="https://fonts.googleapis.com/" crossorigin><link rel="dns-prefetch preconnect" href="https://fonts.gstatic.com/" crossorigin>',
34 },
35 ], () => {
36 const hints = renderer.getResourceHints();
37 1 expect(hints).to.have.length(2);
38 1 hints.forEach(hint => {
39 expect(hint.cors).to.equals(resourceHintAllowedCors.anonymousCors);
40 2 expect(hint.type).to.equals(resourceHintAllowedTypes.resourceHintFontType);
41 2 expect(hint.state).to.equals(resourceHintAllowedStates.preconnectResourceHintState);
42 });
43 1 done();
44 });
45 });
46 });
47

spec/helpers/setURLQueryParam.js

100%
81
81
0
Line Lint Hits Source
1 1 const Lab = require('lab'),
2 lab = exports.lab = Lab.script(),
3 describe = lab.experiment,
4 it = lab.it,
5 specHelpers = require('../spec-helpers'),
6 testRunner = specHelpers.testRunner,
7 renderString = specHelpers.renderString;
8
9
10 1 describe('setURLQueryParam helper', function() {
11 const context = {
12 image_url: 'http://example.com/image.png',
13 not_a_url: null,
14 key: 'referrer',
15 value: 'google',
16 };
17
18 1 const runTestCases = testRunner({context});
19
20 1 it('should return a URL with an added parameter given correct input', function(done) {
21 runTestCases([
22 {
23 input: '{{setURLQueryParam "http://example.com/image.jpg" key value}}',
24 output: `http://example.com/image.jpg?${context.key}=${context.value}`,
25 },
26 {
27 input: '{{setURLQueryParam image_url "key" "value"}}',
28 output: `${context.image_url}?key=value`,
29 },
30 {
31 input: '{{setURLQueryParam (setURLQueryParam image_url "key" "value") "key2" "value2"}}',
32 output: `${context.image_url}?key=value&key2=value2`,
33 },
34 {
35 input: '{{setURLQueryParam "http://example.com/product-1?sku=abc123" "key" "value"}}',
36 output: 'http://example.com/product-1?sku=abc123&key=value',
37 },
38 ], done);
39 });
40
41 1 it('should return a URL with an updated parameter given correct input', function(done) {
42 runTestCases([
43 {
44 input: '{{setURLQueryParam "http://example.com/image.jpg?c=2" "c" "3"}}',
45 output: `http://example.com/image.jpg?c=3`,
46 },
47 {
48 input: '{{setURLQueryParam "http://example.com/image.jpg?a=1&c=2" "c" "3"}}',
49 output: `http://example.com/image.jpg?a=1&c=3`,
50 },
51 {
52 input: '{{setURLQueryParam (setURLQueryParam "http://example.com/image.jpg?a=1&c=2" "a" "2") "c" "3"}}',
53 output: `http://example.com/image.jpg?a=2&c=3`,
54 },
55 {
56 input: '{{setURLQueryParam "http://example.com/product-1?sku=abc123" "sku" "def456"}}',
57 output: 'http://example.com/product-1?sku=def456',
58 },
59 ], done);
60 });
61
62 1 it('should throw an exception if a url is passed with no parameters', function (done) {
63 renderString('{{setURLQueryParam "http://example.com/image.jpg"}}').catch(e => {
64 renderString('{{setURLQueryParam urlData}}').catch(e => {
65 done();
66 });
67 });
68 });
69
70 1 it('should throw an exception if a url is passed with only one parameter', function (done) {
71 renderString('{{setURLQueryParam "http://example.com/image.jpg" "key"}}').catch(e => {
72 renderString('{{setURLQueryParam urlData key}}').catch(e => {
73 done();
74 });
75 });
76 });
77
78 1 it('should throw an exception if an invalid URL is passed', function (done) {
79 renderString('{{setURLQueryParam not_a_url key value}}').catch(e => {
80 renderString('{{setURLQueryParam not_a_url "key" "value"}}').catch(e => {
81 renderString('{{setURLQueryParam "not_a_url" "key" "value"}}').catch(e => {
82 renderString('{{setURLQueryParam "not_a_url" key value}}').catch(e => {
83 done();
84 });
85 });
86 });
87 });
88 });
89 });
90

spec/helpers/snippets.js

100%
16
16
0
Line Lint Hits Source
1 1 const Lab = require('lab'),
2 lab = exports.lab = Lab.script(),
3 describe = lab.experiment,
4 it = lab.it,
5 testRunner = require('../spec-helpers').testRunner;
6
7 1 describe('snippet helper', function() {
8
9 const runTestCases = testRunner({});
10
11 1 it('should render a comment', function(done) {
12 runTestCases([
13 {
14 input: '{{{snippet "header"}}}',
15 output: '<!-- snippet location header -->',
16 },
17 ], done);
18 });
19 });
20

spec/helpers/strReplace.js

100%
71
71
0
Line Lint Hits Source
1 1 const Lab = require('lab'),
2 lab = exports.lab = Lab.script(),
3 describe = lab.experiment,
4 it = lab.it,
5 specHelpers = require('../spec-helpers'),
6 testRunner = require('../spec-helpers').testRunner,
7 renderString = specHelpers.renderString;;
8
9 1 describe('strReplace helper', function() {
10 const context = {
11 string: "My name is Albe Albe Albe",
12 substr: "Albe",
13 newSubstr: "Alex",
14 object: {}
15 };
16
17 1 const runTestCases = testRunner({context});
18
19 1 it('should replace all by default', function(done) {
20 runTestCases([
21 {
22 input: '{{strReplace string substr newSubstr}}',
23 output: 'My name is Alex Alex Alex',
24 },
25 {
26 input: '{{strReplace "Your name is none" "none" "Bob"}}',
27 output: 'Your name is Bob',
28 },
29 ], done);
30 });
31
32 1 it('should remove characters from string', function(done) {
33 runTestCases([
34 {
35 input: '{{strReplace "123-45-6789" "-" ""}}',
36 output: '123456789',
37 },
38 ], done);
39 });
40
41 1 it('should replace multiple if given quantity', function(done) {
42 runTestCases([
43 {
44 input: '{{strReplace string substr newSubstr -5}}',
45 output: 'My name is Albe Albe Albe',
46 },
47 {
48 input: '{{strReplace string substr newSubstr 0}}',
49 output: 'My name is Albe Albe Albe',
50 },
51 {
52 input: '{{strReplace string substr newSubstr 2}}',
53 output: 'My name is Alex Alex Albe',
54 },
55 {
56 input: '{{strReplace string substr newSubstr 4}}',
57 output: 'My name is Alex Alex Alex',
58 },
59 {
60 input: '{{strReplace string substr newSubstr 100}}',
61 output: 'My name is Alex Alex Alex',
62 },
63 ], done);
64 });
65
66 1 it('should throw an exception if the parameters have an invalid type', function(done) {
67 renderString('{{strReplace object "none" "Bob"}}').catch(e => {
68 renderString('{{strReplace "none" 3 "Bob"}}').catch(e => {
69 renderString('{{strReplace "none" "Bob" object}}').catch(e => {
70 renderString('{{strReplace string substr newSubstr "3"}}').catch(e => {
71 done();
72 });
73 });
74 });
75 });
76 });
77 });
78

spec/helpers/stripQuerystring.js

100%
39
39
0
Line Lint Hits Source
1 1 const Lab = require('lab'),
2 lab = exports.lab = Lab.script(),
3 describe = lab.experiment,
4 it = lab.it,
5 testRunner = require('../spec-helpers').testRunner;
6
7 1 const themeSettings = {
8 logo_image: '600x300',
9 };
10
11 1 describe('stripQuerystring helper', function() {
12 const urlData_2_qs = 'https://cdn.example.com/path/to/{:size}/image.png?c=2&imbypass=on';
13 1 const context = {
14 image_with_2_qs: {
15 data: urlData_2_qs
16 },
17 };
18
19 1 const runTestCases = testRunner({context, themeSettings});
20
21 1 it('strips the query string from a given url', function(done) {
22 runTestCases([{
23 input: `{{stripQuerystring 'http://www.example.com?foo=1&bar=2&baz=3'}}`,
24 output: 'http://www.example.com',
25 },
26 ], done);
27 });
28
29 1 it('should work with the getImageSrcset helper as a nested expression', function(done) {
30 runTestCases([{
31 input: `{{stripQuerystring (getImageSrcset image_with_2_qs 1x='100x100')}}`,
32 output: 'https://cdn.example.com/path/to/100x100/image.png',
33 },
34 ], done);
35 });
36
37 1 it('should work with the getImage helper as a nested expression', function(done) {
38 runTestCases([
39 {
40 input: '{{stripQuerystring (getImage image_with_2_qs "logo_image")}}',
41 output: 'https://cdn.example.com/path/to/600x300/image.png',
42 },
43 ], done);
44 });
45 });
46

spec/helpers/stylesheet.js

100%
115
115
0
Line Lint Hits Source
1 1 const Lab = require('lab'),
2 lab = exports.lab = Lab.script(),
3 describe = lab.experiment,
4 it = lab.it;
5 1 const {buildRenderer, testRunner} = require("../spec-helpers");
6 1 const {expect} = require("code");
7 1 const {resourceHintStyleType} = require("../../helpers/lib/resourceHints").resourceHintAllowedTypes;
8 1 const {anonymousCors, noCors} = require("../../helpers/lib/resourceHints").resourceHintAllowedCors;
9
10 1 describe('stylesheet helper', () => {
11 const siteSettings = {
12 cdn_url: 'https://cdn.bcapp/hash',
13 theme_version_id: '123',
14 theme_config_id: 'xyz',
15 };
16
17 1 const runTestCases = testRunner({siteSettings});
18
19 1 it('should render a link tag with the cdn ulr and stencil-stylesheet data tag', done => {
20 const renderer = buildRenderer(siteSettings);
21 1 const runner = testRunner({renderer});
22 1 runner([
23 {
24 input: '{{{stylesheet "assets/css/style.css" crossorigin="anonymous"}}}',
25 output: '<link data-stencil-stylesheet href="https://cdn.bcapp/hash/stencil/123/css/style-xyz.css" rel="stylesheet" crossorigin="anonymous">',
26 },
27 ], () => {
28 const hints = renderer.getResourceHints();
29 1 expect(hints.length).to.equals(1);
30 1 hints.forEach(({src, type, state, cors}) => {
31 expect(src).to.startWith(siteSettings.cdn_url);
32 1 expect(type).to.equals(resourceHintStyleType);
33 1 expect(state).to.equals('preload');
34 1 expect(cors).to.equals(anonymousCors);
35 });
36 1 done();
37 });
38 });
39
40 1 it('should render both link tags correclty but produce only one resource hint', done => {
41 const renderer = buildRenderer(siteSettings);
42 1 const context = {
43 globals: {
44 resourceHints: [
45 {src: 'https://cdn.bcapp/hash/stencil/123/css/style-xyz.css'}
46 ]
47 }
48 };
49 1 const runner = testRunner({context, renderer});
50 1 runner([
51 {
52 input: '{{{stylesheet "assets/css/style.css" crossorigin="anonymous"}}} {{{stylesheet "assets/css/style.css" crossorigin="anonymous"}}}',
53 output: '<link data-stencil-stylesheet href="https://cdn.bcapp/hash/stencil/123/css/style-xyz.css" rel="stylesheet" crossorigin="anonymous"> <link data-stencil-stylesheet href="https://cdn.bcapp/hash/stencil/123/css/style-xyz.css" rel="stylesheet" crossorigin="anonymous">',
54 context
55 },
56 ], () => {
57 const hints = renderer.getResourceHints();
58 1 expect(hints.length).to.equals(1);
59 1 hints.forEach(({src, type, state, cors}) => {
60 expect(src).to.startWith(siteSettings.cdn_url);
61 1 expect(type).to.equals(resourceHintStyleType);
62 1 expect(state).to.equals('preload');
63 1 expect(cors).to.equals(anonymousCors);
64 });
65 1 done();
66 });
67 });
68
69 1 it('should render a link tag and all extra attributes with no cdn url', done => {
70 runTestCases([
71 {
72 input: '{{{stylesheet "assets/css/style.css" blah rel="something" class="myStyle"}}}',
73 output: '<link data-stencil-stylesheet href="/assets/css/style.css" rel="something" class="myStyle">',
74 siteSettings: {},
75 },
76 ], done);
77 });
78
79 1 it('should render a link with empty href and no resource hint', done => {
80 const template = '{{{stylesheet "" }}}';
81
82 1 const renderer = buildRenderer(siteSettings);
83 1 renderer.renderString(template, {})
84 .then(r => {
85 expect(r).to.equals('<link data-stencil-stylesheet href="" rel="stylesheet">');
86 1 const hints = renderer.getResourceHints();
87 1 expect(hints).to.have.length(0);
88 1 done();
89 })
90 .catch(done);
91 });
92
93 1 it('should add configId to the filename', done => {
94 const siteSettings = {theme_config_id: 'foo'};
95 1 const renderer = buildRenderer(siteSettings);
96 1 const runner = testRunner({renderer});
97 1 const src = '/assets/css/style-foo.css';
98 1 runner([
99 {
100 input: '{{{stylesheet "assets/css/style.css" }}}',
101 output: `<link data-stencil-stylesheet href="${src}" rel="stylesheet">`,
102 siteSettings: siteSettings,
103 },
104 ], () => {
105 const hints = renderer.getResourceHints();
106 1 expect(hints.length).to.equals(1);
107 1 const hint = hints[0];
108 1 expect(hint.src).to.equals(src);
109 1 expect(hint.state).to.equals('preload');
110 1 expect(hint.cors).to.equals(noCors);
111 1 done();
112 });
113 });
114
115 1 it('should not append configId if the file is not in assets/css/ directory', done => {
116 runTestCases([
117 {
118 input: '{{{stylesheet "assets/lib/style.css" }}}',
119 output: '<link data-stencil-stylesheet href="/assets/lib/style.css" rel="stylesheet">',
120 siteSettings: { theme_config_id: 'foo' },
121 },
122 ], done);
123 });
124 });
125

spec/helpers/thirdParty.js

100%
198
198
0
Line Lint Hits Source
1 1 const Lab = require('lab'),
2 lab = exports.lab = Lab.script(),
3 describe = lab.experiment,
4 it = lab.it,
5 testRunner = require('../spec-helpers').testRunner;
6
7 1 describe('third party handlebars-helpers', function() {
8 const context = {
9 array: [1, 2, 3, 4, 5],
10 null: null,
11 object: {"a" : 1, "b" : 2},
12 options: { a: { b: { c: 'd' } } }
13 };
14
15 1 const runTestCases = testRunner({context});
16
17 1 describe('array helpers', function() {
18
19 describe('after helper', function() {
20 it('returns all the items in an array after the index', function(done) {
21 runTestCases([
22 {
23 input: '{{after array 1}}',
24 output: '2,3,4,5',
25 },
26 ], done);
27 });
28 });
29
30 1 describe('first helper', function() {
31 it('returns the first n items in an array', function(done) {
32 runTestCases([
33 {
34 input: '{{first array 2}}',
35 output: '1,2',
36 },
37 ], done);
38 });
39 });
40
41 });
42
43 1 describe('collection helpers', function() {
44
45 describe('length helper', function() {
46 it('returns the length of the array', function(done) {
47 runTestCases([
48 {
49 input: '{{length array}}',
50 output: '5',
51 },
52 ], done);
53 });
54 });
55
56 });
57
58 1 describe('comparison helpers', function() {
59
60 describe('contains helper', function() {
61 it('renders the contains block if it evaluates to true', function(done) {
62 runTestCases([
63 {
64 input: `{{#contains array 1}}This will be rendered.{{else}}This will not be rendered.{{/contains}}`,
65 output: 'This will be rendered.',
66 },
67 ], done);
68 });
69
70 1 it('renders the else block if it evaluates to false', function(done) {
71 runTestCases([
72 {
73 input: `{{#contains array '1'}}This will not be rendered.{{else}}This will be rendered.{{/contains}}`,
74 output: 'This will be rendered.',
75 },
76 ], done);
77 });
78 1 it('renders the contains block if it evaluates to true. object as input', function(done) {
79 runTestCases([
80 {
81 input: `{{#contains object 1}}This will be rendered.{{else}}This will not be rendered.{{/contains}}`,
82 output: 'This will be rendered.',
83 },
84 ], done);
85 });
86 1 it('renders the else block if it evaluates to false. object as input', function(done) {
87 runTestCases([
88 {
89 input: `{{#contains object '3'}}This will not be rendered.{{else}}This will be rendered.{{/contains}}`,
90 output: 'This will be rendered.',
91 },
92 ], done);
93 });
94 1 it('renders the else block if it evaluates to false. null / number as input', function(done) {
95 runTestCases([
96 {
97 input: `{{#contains null '3'}}This will not be rendered.{{else}}This will be rendered.{{/contains}}`,
98 output: 'This will be rendered.',
99 },
100 {
101 input: `{{#contains 111 '3'}}This will not be rendered.{{else}}This will be rendered.{{/contains}}`,
102 output: 'This will be rendered.',
103 },
104 ], done);
105 });
106 });
107
108 });
109
110 1 describe('html helpers', function() {
111
112 describe('contains ellipsis', function() {
113 it('truncates a string to the specified length and appends an ellipsis', function(done) {
114 runTestCases([
115 {
116 input: `{{ellipsis "<span>foo bar baz</span>" 7}}`,
117 output: 'foo bar…',
118 },
119 ], done);
120 });
121 });
122
123 1 describe('contains thumbnailImage', function() {
124 it('creates a <figure> with a thumbnail linked to an image', function(done) {
125 const context = {
126 data: {
127 id: 'id',
128 alt: 'alt',
129 thumbnail: 'thumbnail.png',
130 size: {
131 width: 32,
132 height: 32
133 }
134 }
135 };
136
137 1 runTestCases([
138 {
139 input: `{{{thumbnailImage data}}}`,
140 output: '<figure id=\"image-id\">\n<img alt=\"alt\" src=\"thumbnail.png\" width=\"32\" height=\"32\">\n</figure>',
141 context: context,
142 },
143 ], done);
144 });
145 });
146
147 });
148
149 1 describe('inflection helpers', function() {
150
151 describe('contains ordinalize', function() {
152 it('returns an ordinalized number as a string', function(done) {
153 runTestCases([
154 {
155 input: `{{ordinalize 42}}`,
156 output: '42nd',
157 },
158 ], done);
159 });
160 });
161
162 });
163
164 1 describe('markdown helpers', function() {
165
166 describe('contains markdown', function() {
167 it('converts a string of markdown to HTML', function(done) {
168 runTestCases([
169 {
170 input: `{{#markdown}}# Foo{{/markdown}}`,
171 output: '<h1>Foo</h1>\n',
172 },
173 ], done);
174 });
175 });
176
177 });
178
179 1 describe('math helpers', function() {
180
181 describe('contains avg', function() {
182 it('returns the average of the numbers in an array', function(done) {
183 runTestCases([
184 {
185 input: `{{avg array}}`,
186 output: '3',
187 },
188 ], done);
189 });
190 });
191
192 });
193
194 1 describe('object helpers', function() {
195
196 describe('contains isObject', function() {
197 it('returns true if the value is an object', function(done) {
198 runTestCases([
199 {
200 input: `{{isObject options}}`,
201 output: 'true',
202 },
203 ], done);
204 });
205
206 1 it('returns false if the value is not an object', function(done) {
207 runTestCases([
208 {
209 input: `{{isObject "foo"}}`,
210 output: 'false',
211 },
212 ], done);
213 });
214 });
215
216 });
217
218 1 describe('string helpers', function() {
219
220 describe('contains capitalize', function() {
221 it('capitalizes the first word in a sentence', function(done) {
222 runTestCases([
223 {
224 input: `{{capitalize "foo bar baz"}}`,
225 output: 'Foo bar baz',
226 },
227 ], done);
228 });
229 });
230
231 });
232 });
233

spec/helpers/toLowerCase.js

100%
46
46
0
Line Lint Hits Source
1 1 const Lab = require('lab'),
2 lab = exports.lab = Lab.script(),
3 describe = lab.experiment,
4 it = lab.it,
5 testRunner = require('../spec-helpers').testRunner;
6
7 1 describe('toLowerCase helper', function() {
8 const context = {
9 string: "I Love PIZZA",
10 number: 365,
11 object: {},
12 array: [1, 2, 3]
13 };
14
15 1 const runTestCases = testRunner({context});
16
17 1 it('should convert string to lower case', function(done) {
18 runTestCases([
19 {
20 input: '{{toLowerCase string}}',
21 output: 'i love pizza',
22 },
23 {
24 input: '{{toLowerCase "HELLO"}}',
25 output: 'hello',
26 },
27 ], done);
28 });
29
30 1 it('should properly handle values other than strings', function(done) {
31 runTestCases([
32 {
33 input: '{{toLowerCase number}}',
34 output: '365',
35 },
36 {
37 input: '{{toLowerCase 5}}',
38 output: '5',
39 },
40 {
41 input: '{{toLowerCase object}}',
42 output: '[object Object]',
43 },
44 {
45 input: '{{toLowerCase array}}',
46 output: '1,2,3',
47 },
48 ], done);
49 });
50 });
51

spec/helpers/truncate.js

100%
77
77
0
Line Lint Hits Source
1 1 const Lab = require('lab'),
2 lab = exports.lab = Lab.script(),
3 describe = lab.experiment,
4 it = lab.it,
5 testRunner = require('../spec-helpers').testRunner;
6
7 1 describe('truncate helper', function() {
8 const context = {
9 chinese_string: '𠜎𠜱𠝹𠱓𠱸𠲖𠳏',
10 number: 2,
11 spanish_string: 'mañana',
12 string: 'hello world',
13 unicode_string: 'She ❤️️ this',
14 empty_string: '',
15 null: null,
16 };
17
18 1 const runTestCases = testRunner({context});
19
20 1 it('should return the entire string if length is longer than the input string', function(done) {
21 runTestCases([
22 {
23 input: '{{truncate string 15}}',
24 output: 'hello world',
25 },
26 ], done);
27 });
28
29 1 it('should return the first length number of characters', function(done) {
30 runTestCases([
31 {
32 input: '{{truncate string 5}}',
33 output: 'hello',
34 },
35 ], done);
36 });
37
38 1 it('should return the first argument, coerced to a string, if it is not a string', function(done) {
39 runTestCases([
40 {
41 input: '{{truncate number 5}}',
42 output: '2',
43 },
44 ], done);
45 });
46
47 1 it('should handle non-English strings', function(done) {
48 runTestCases([
49 {
50 input: '{{truncate spanish_string 3}}',
51 output: 'mañ',
52 },
53 {
54 input: '{{truncate chinese_string 3}}',
55 output: '𠜎𠜱𠝹',
56 },
57 ], done);
58 });
59
60 1 it('should handle empty strings', function(done) {
61 runTestCases([
62 {
63 input: '{{truncate empty_string 5}}',
64 output: '',
65 },
66 ], done);
67 });
68
69 1 it('should handle null object', function(done) {
70 runTestCases([
71 {
72 input: '{{truncate null 5}}',
73 output: '',
74 },
75 ], done);
76 });
77
78 1 it('should handle unicode strings', function(done) {
79 runTestCases([
80 {
81 input: '{{truncate unicode_string 5}}',
82 output: 'She ❤️',
83 },
84 ], done);
85 });
86 });
87

spec/helpers/typeof.js

100%
106
106
0
Line Lint Hits Source
1 1 const { describe, it } = exports.lab = require('lab').script();
2
3 1 const { testRunner, renderString} = require('../spec-helpers');
4
5 1 describe('typeof inline', () => {
6 const context = {
7 string: 'John Doe',
8 number: 123,
9 boolean: true,
10 nullValue: null,
11 undefinedValue: undefined,
12 objectValue: { name: 'John' },
13 array: ['John', 'Doe'],
14 // eslint-disable-next-line no-undef
15 bigint: BigInt(12345678901234567890),
16 functionValue: () => {},
17 };
18 1 const runTestCases = testRunner({context});
19
20 1 it('should return string', done => {
21 runTestCases([
22 {
23 input: '{{typeof string}}',
24 output: 'string',
25 },
26 ], done);
27 });
28
29 1 it('should return number', done => {
30 runTestCases([
31 {
32 input: '{{typeof number}}',
33 output: 'number',
34 },
35 ], done);
36 });
37
38 1 it('should return boolean', done => {
39 runTestCases([
40 {
41 input: '{{typeof boolean}}',
42 output: 'boolean',
43 },
44 ], done);
45 });
46
47 1 it('should return object: null', done => {
48 runTestCases([
49 {
50 input: '{{typeof nullValue}}',
51 output: 'object', // Null is an object in JavaScript
52 },
53 ], done);
54 });
55
56 1 it('should return object: undefined', done => {
57 runTestCases([
58 {
59 input: '{{typeof undefinedValue}}',
60 output: 'undefined',
61 },
62 ], done);
63 });
64
65 1 it('should return object: object', done => {
66 runTestCases([
67 {
68 input: '{{typeof objectValue}}',
69 output: 'object',
70 },
71 ], done);
72 });
73
74 1 it('should return object: array', done => {
75 runTestCases([
76 {
77 input: '{{typeof array}}',
78 output: 'object',
79 },
80 ], done);
81 });
82
83 1 it('should return bigInt', done => {
84 runTestCases([
85 {
86 input: '{{typeof bigint}}',
87 output: 'bigint',
88 },
89 ], done);
90 });
91
92 1 it('should return function', done => {
93 runTestCases([
94 {
95 input: '{{typeof functionValue}}',
96 output: 'function',
97 },
98 ], done);
99 });
100
101
102 1 it('should work with output from another function', done => {
103 runTestCases([
104 {
105 input: '{{typeof (multiConcat string number)}}',
106 output: 'string',
107 },
108 ], done);
109 });
110
111 1 it('should not accept more than one value, throw a error', function(done) {
112 renderString("{{typeof number string}}").catch(_ => {
113 done();
114 });
115 });
116
117 1 it('should not work as a block helper, throw a error', function(done) {
118 renderString("{{#typeof number}}").catch(_ => {
119 done();
120 });
121 });
122 });
123

spec/helpers/3p/array.js

100%
482
482
0
Line Lint Hits Source
1 /**
2 * https://github.com/helpers/handlebars-helpers/blob/0.8.4/test/array.js
3 */
4
5 1 const Code = require('code');
6 1 const Lab = require('lab');
7 1 const lab = exports.lab = Lab.script();
8 1 const expect = Code.expect;
9 1 const it = lab.it;
10 1 const describe = lab.describe
11
12 1 const { buildRenderer } = require('../../spec-helpers');
13 1 const renderer = buildRenderer();
14 1 const hbs = renderer.handlebars;
15 1 const helpers = require('../../../helpers/3p/array');
16
17 1 Object.keys(helpers).forEach(key => {
18 hbs.registerHelper(key, helpers[key]);
19 })
20
21
22 1 var context = {
23 array: ['a', 'b', 'c', 'd', 'e', 'f', 'g', 'h'],
24 notArray: null,
25 };
26
27 1 describe('array', function() {
28 describe('after', function() {
29 it('should return an empty string when undefined.', function(done) {
30 expect(hbs.compile('{{after}}')()).to.equal('');
31 1 done();
32 });
33
34 1 it('should return all of the items in an array after the given index.', function(done) {
35 var fn = hbs.compile('{{after array 5}}');
36 1 expect(fn(context)).to.equal(['f', 'g', 'h'].toString());
37 1 done();
38 });
39
40 1 it('should return all of the items in an array after the specified count.', function(done) {
41 var fn = hbs.compile('{{after notArray 5}}');
42 1 expect(fn(context)).to.equal('');
43 1 done();
44 });
45
46 1 it('should return all of the items in an array after the specified count.', function(done) {
47 var fn = hbs.compile('{{after array 5}}');
48 1 expect(fn(context)).to.equal(['f', 'g', 'h'].toString());
49 1 done();
50 });
51 });
52
53 1 describe('arrayify', function() {
54 it('should arrayify a value.', function(done) {
55 expect(hbs.compile('{{#each (arrayify .)}}{{.}}{{/each}}')('foo')).to.equal('foo');
56 1 expect(hbs.compile('{{#each (arrayify .)}}{{.}}{{/each}}')(['foo'])).to.equal('foo');
57 1 done();
58 });
59 });
60
61 1 describe('before', function() {
62 it('should return an empty string when undefined.', function(done) {
63 expect(hbs.compile('{{before}}')()).to.equal('');
64 1 done();
65 });
66 1 it('should return all of the items in an array before the given index.', function(done) {
67 var fn = hbs.compile('{{before array 5}}');
68 1 expect(fn(context)).to.equal(['a', 'b', 'c'].toString());
69 1 done();
70 });
71
72 1 it('should return all of the items in an array before the specified count.', function(done) {
73 var fn = hbs.compile('{{before array 5}}');
74 1 expect(fn(context)).to.equal(['a', 'b', 'c'].toString());
75 1 done();
76 });
77 });
78
79 1 describe('each', function() {
80 it('should use the key and value of each property in an object inside a block.', function(done) {
81 var fn = hbs.compile('{{#each obj}}{{@key}}: {{this}} {{/each}}');
82 1 expect(fn({obj: {fry: 3, bender: 120 }})).to.equal('fry: 3 bender: 120 ');
83 1 done();
84 });
85 });
86
87 1 describe('eachIndex', function() {
88 it('should render the block using the array and each item\'s index.', function(done) {
89 var fn = hbs.compile('{{#eachIndex array}} {{item}} is {{index}} {{/eachIndex}}');
90 1 expect(fn(context)).to.equal(' a is 0 b is 1 c is 2 d is 3 e is 4 f is 5 g is 6 h is 7 ');
91 1 done();
92 });
93
94 1 it('should render the block when non array is passed returning empty string', function(done) {
95 var fn = hbs.compile('{{#eachIndex notArray}} {{item}} is {{index}} {{/eachIndex}}');
96 1 expect(fn(context)).to.equal('');
97 1 done();
98 });
99 });
100
101 1 describe('first', function() {
102 describe('array', function () {
103 it('should return the first item in a collection.', function(done) {
104 var fn = hbs.compile('{{first foo}}');
105 1 expect(fn({foo: ['a', 'b', 'c']})).to.equal('a');
106 1 done();
107 });
108
109 1 it('should return an array with the first two items in a collection.', function(done) {
110 var fn = hbs.compile('{{first foo 2}}');
111 1 expect(fn({foo: ['a', 'b', 'c']})).to.equal(['a', 'b'].toString());
112 1 done();
113 });
114
115 1 it('should return an empty string when undefined.', function(done) {
116 expect(hbs.compile('{{first}}')()).to.equal('');
117 1 done();
118 });
119
120 1 it('should return the first item in an array.', function(done) {
121 var fn = hbs.compile('{{first foo}}');
122 1 expect(fn({foo: ['a', 'b', 'c']})).to.equal('a');
123 1 done();
124 });
125
126 1 it('should return an array with the first two items in an array.', function(done) {
127 var fn = hbs.compile('{{first foo 2}}');
128 1 expect(fn({foo: ['a', 'b', 'c']})).to.equal(['a', 'b'].toString());
129 1 done();
130 });
131 });
132
133 1 describe('string', function () {
134 const context = {
135 myString: 'BigCommerce',
136 emptyString: '',
137 smallString: 'abc'
138 };
139
140 1 it('should return an empty string when empty string is provided.', function (done) {
141 expect(hbs.compile('{{first emptyString}}')(context)).to.equal('');
142 1 expect(hbs.compile('{{first emptyString 2}}')(context)).to.equal('');
143 1 done();
144 });
145
146 1 it('should return the whole string when the number is big enough.', function (done) {
147 expect(hbs.compile('{{first smallString 5}}')(context)).to.equal('abc');
148 1 done();
149 });
150
151 1 it('should return the expected substring.', function (done) {
152 expect(hbs.compile('{{first myString 5}}')(context)).to.equal('BigCo');
153 1 done();
154 });
155 });
156 });
157
158 1 describe('filter', function() {
159 it('should render the block if the given string is in the array.', function(done) {
160 var source = '{{#filter array "d"}}AAA{{else}}BBB{{/filter}}';
161 1 expect(hbs.compile(source)(context)).to.equal('AAA');
162 1 done();
163 });
164
165 1 it('should render the inverse block if the string is not in the array:', function(done) {
166 var source = '{{#filter array "foo"}}AAA{{else}}BBB{{/filter}}';
167 1 expect(hbs.compile(source)(context)).to.equal('BBB');
168 1 done();
169 });
170
171 1 it('should render a block for each object that has a "first" property with the value "d".', function(done) {
172
173 var ctx = {
174 collection: [
175 {first: 'aaa', last: 'bbb'},
176 {first: 'b'},
177 {title: 'ccc', last: 'ddd'},
178 {first: 'd'},
179 {first: 'eee', last: 'fff'},
180 {first: 'f'},
181 {title: 'ggg', last: 'hhh'},
182 {first: 'h'}
183 ]
184 };
185
186 1 var source = '{{#filter collection "d" property="first"}}{{this.first}}{{else}}ZZZ{{/filter}}';
187 1 var fn = hbs.compile(source);
188 1 expect(fn(ctx)).to.equal('d');
189 1 done();
190 });
191 });
192
193 1 describe('forEach', function() {
194 it('should iterate over an array, exposing objects as context.', function(done) {
195 var arr = [{name: 'a'}, {name: 'b'}, {name: 'c'}];
196
197 1 var fn = hbs.compile('{{#forEach arr}}{{name}}{{/forEach}}');
198 1 expect(fn({arr: arr})).to.equal('abc');
199 1 done();
200 });
201
202 1 it('should expose `index`', function(done) {
203 var arr = [{name: 'a'}, {name: 'b'}, {name: 'c'}];
204
205 1 var fn = hbs.compile('{{#forEach arr}}{{index}}{{/forEach}}');
206 1 expect(fn({arr: arr})).to.equal('123');
207 1 done();
208 });
209
210 1 it('should expose `total`', function(done) {
211 var arr = [{name: 'a'}, {name: 'b'}, {name: 'c'}];
212
213 1 var fn = hbs.compile('{{#forEach arr}}{{total}}{{/forEach}}');
214 1 expect(fn({arr: arr})).to.equal('333');
215 1 done();
216 });
217
218 1 it('should expose `isFirst`', function(done) {
219 var arr = [{name: 'a'}, {name: 'b'}, {name: 'c'}];
220
221 1 var fn = hbs.compile('{{#forEach arr}}{{isFirst}}{{/forEach}}');
222 1 expect(fn({arr: arr})).to.equal('truefalsefalse');
223 1 done();
224 });
225
226 1 it('should expose `isLast`', function(done) {
227 var arr = [{name: 'a'}, {name: 'b'}, {name: 'c'}];
228
229 1 var fn = hbs.compile('{{#forEach arr}}{{isLast}}{{/forEach}}');
230 1 expect(fn({arr: arr})).to.equal('falsefalsetrue');
231 1 done();
232 });
233
234 1 it('should return empty block', function(done) {
235 let arr = [];
236 1 var fn = hbs.compile('{{#forEach arr}}{{isLast}}{{/forEach}}');
237 1 expect(fn({arr})).to.equal('');
238
239 1 arr = null;
240 1 var fn = hbs.compile('{{#forEach arr}}{{isLast}}{{/forEach}}');
241 1 expect(fn({arr})).to.equal('');
242 1 done();
243 });
244 });
245
246 1 describe('inArray', function() {
247 it('should render the first block when a value exists in the array.', function(done) {
248 var fn = hbs.compile('{{#inArray array "d"}}AAA{{else}}BBB{{/inArray}}');
249 1 expect(fn(context)).to.equal('AAA');
250 1 done();
251 });
252
253 1 it('should render the inverse block when a value does not exist.', function(done) {
254 var fn = hbs.compile('{{#inArray array "foo"}}AAA{{else}}BBB{{/inArray}}');
255 1 expect(fn(context)).to.equal('BBB');
256 1 done();
257 });
258 });
259
260 1 describe('isArray', function() {
261 it('should return true if the value is an array.', function(done) {
262 expect(hbs.compile('{{isArray "foo"}}')()).to.equal('false');
263 1 expect(hbs.compile('{{isArray \'["foo"]\'}}')()).to.equal('false');
264 1 expect(hbs.compile('{{isArray foo}}')({foo: ['foo']})).to.equal('true');
265 1 expect(hbs.compile('{{isArray (arrayify "foo")}}')()).to.equal('true');
266 1 expect(hbs.compile('{{isArray (arrayify ["foo"])}}')()).to.equal('true');
267 1 done();
268 });
269 });
270
271 1 describe('last', function() {
272 describe('array', function () {
273 it('should return an empty string when undefined.', function(done) {
274 expect(hbs.compile('{{last}}')()).to.equal('');
275 1 done();
276 });
277
278 1 it('should return the last item in an array.', function(done) {
279 expect(hbs.compile('{{last array}}')(context)).to.equal('h');
280 1 done();
281 });
282
283 1 it('should return an array with the last two items in an array.', function(done) {
284 expect(hbs.compile('{{last array 2}}')(context)).to.equal(['g', 'h'].toString());
285 1 done();
286 });
287
288 1 it('should return an empty array array if non array is passed', function(done) {
289 expect(hbs.compile('{{last notArray 2}}')(context)).to.equal([].toString());
290 1 done();
291 });
292 });
293
294 1 describe('string', function () {
295 const context = {
296 myString: 'BigCommerce',
297 emptyString: '',
298 smallString: 'abc'
299 };
300
301 1 it('should return an empty string when empty string is provided.', function (done) {
302 expect(hbs.compile('{{last emptyString}}')(context)).to.equal('');
303 1 expect(hbs.compile('{{last emptyString 2}}')(context)).to.equal('');
304 1 done();
305 });
306
307 1 it('should return the whole string when the number is big enough.', function (done) {
308 expect(hbs.compile('{{last smallString 5}}')(context)).to.equal('abc');
309 1 done();
310 });
311
312 1 it('should return the expected substring.', function (done) {
313 expect(hbs.compile('{{last myString 5}}')(context)).to.equal('merce');
314 1 done();
315 });
316 });
317 });
318
319 1 describe('lengthEqual', function() {
320 it('should render the first block if length is the given number', function(done) {
321 var fn = hbs.compile('{{#lengthEqual array 8}}AAA{{else}}BBB{{/lengthEqual}}');
322 1 expect(fn(context)).to.equal('AAA');
323 1 done();
324 });
325
326 1 it('should render the inverse block if length is not the given number', function(done) {
327 var fn = hbs.compile('{{#lengthEqual array 3}}AAA{{else}}BBB{{/lengthEqual}}');
328 1 expect(fn(context)).to.equal('BBB');
329 1 done();
330 });
331 });
332
333 1 describe('map', function() {
334 it('should return an empty string when undefined.', function(done) {
335 expect(hbs.compile('{{map}}')()).to.equal('');
336 1 done();
337 });
338
339 1 it('should map the items in the array and return new values.', function(done) {
340 var o = {array: ['a', 'b', 'c']};
341 1 o.double = function(str) {
342 return str + str;
343 };
344 1 var fn = hbs.compile('{{map array double}}');
345 1 expect(fn(o)).to.equal('aa,bb,cc');
346 1 done();
347 });
348
349 1 it('should work with a string value:', function(done) {
350 var o = {};
351 1 o.double = function(str) {
352 return str + str;
353 };
354 1 var fn = hbs.compile('{{map \'["a","b","c"]\' double}}');
355 1 expect(fn(o)).to.equal('aa,bb,cc');
356 1 done();
357 });
358
359 1 it('should return an empty string when the array syntax is invalid:', function(done) {
360 var fn = hbs.compile('{{map \'["b", "c", "a"\'}}');
361 1 expect(fn(context)).to.equal('');
362 1 done();
363 });
364 });
365
366 1 describe('some', function() {
367 it('should render the first block if the callback returns true', function(done) {
368 var ctx = {array: ['a', 'b', 'c']};
369 1 ctx.isString = function(val) {
370 return typeof val === 'string';
371 };
372 1 var fn = hbs.compile('{{#some array isString}}AAA{{else}}BBB{{/some}}');
373 1 expect(fn(ctx)).to.equal('AAA');
374 1 done();
375 });
376
377 1 it('should render the inverse block if the array is undefined', function(done) {
378 var fn = hbs.compile('{{#some array isString}}AAA{{else}}BBB{{/some}}');
379 1 expect(fn()).to.equal('BBB');
380 1 done();
381 });
382
383 1 it('should render the inverse block if falsey', function(done) {
384 var ctx = {array: [['a'], ['b'], ['c']]};
385 1 ctx.isString = function(val) {
386 return typeof val === 'string';
387 };
388 1 var fn = hbs.compile('{{#some array isString}}AAA{{else}}BBB{{/some}}');
389 1 expect(fn(ctx)).to.equal('BBB');
390 1 done();
391 });
392 });
393
394 1 describe('sort', function() {
395 it('should return an empty string when an invalid value is passed:', function(done) {
396 var fn = hbs.compile('{{sort}}');
397 1 var res = fn();
398 1 expect(res).to.equal('');
399 1 done();
400 });
401
402 1 it('should sort the items in the array', function(done) {
403 var fn = hbs.compile('{{sort array}}');
404 1 var res = fn({array: ['c', 'a', 'b']});
405 1 expect(res).to.equal('a,b,c');
406 1 done();
407 });
408
409 1 it('should return all items in an array sorted in lexicographical order.', function(done) {
410 var fn = hbs.compile('{{sort array}}');
411 1 expect(fn(context)).to.equal(['a', 'b', 'c', 'd', 'e', 'f', 'g', 'h'].toString());
412 1 done();
413 });
414
415 1 it('should sort the items in the array in reverse order:', function(done) {
416 var fn = hbs.compile('{{sort array reverse="true"}}');
417 1 var res = fn({array: ['c', 'a', 'b']});
418 1 expect(res).to.equal('c,b,a');
419 1 done();
420 });
421 });
422
423 1 describe('sortBy', function() {
424 it('should return an empty string when undefined.', function(done) {
425 expect(hbs.compile('{{sortBy}}')()).to.equal('');
426 1 done();
427 });
428
429 1 it('should sort the items in an array.', function(done) {
430 var fn = hbs.compile('{{sortBy \'["b", "c", "a"]\'}}');
431 1 expect(fn(context)).to.equal('a,b,c');
432 1 done();
433 });
434
435 1 it('should return an empty string when the array is invalid:', function(done) {
436 var fn = hbs.compile('{{sortBy \'["b", "c", "a"\'}}');
437 1 expect(fn(context)).to.equal('');
438 1 done();
439 });
440
441 1 it('should take a compare function.', function(done) {
442 var o = {};
443 1 o.compare = function(a, b) {
444 return b.localeCompare(a);
445 };
446 1 var fn = hbs.compile('{{sortBy \'["b", "c", "a"]\' compare}}');
447 1 expect(fn(o)).to.equal('c,b,a');
448 1 done();
449 });
450
451 1 it('should sort based on object key:', function(done) {
452 var ctx = {arr: [{a: 'zzz'}, {a: 'aaa'}]};
453
454 1 const helpers = require('../../../helpers/3p/object');
455
456 1 Object.keys(helpers).forEach(key => {
457 hbs.registerHelper(key, helpers[key]);
458 })
459 1 var fn = hbs.compile('{{{stringify (sortBy arr "a") 0}}}');
460 1 expect(fn(ctx)).to.equal('[{"a":"aaa"},{"a":"zzz"}]');
461 1 done();
462 });
463 });
464
465 1 describe('withAfter', function() {
466 it('should use all of the items in an array after the specified count.', function(done) {
467 var fn = hbs.compile('{{#withAfter array 5}}<{{this}}>{{/withAfter}}');
468 1 expect(fn(context)).to.equal('<f><g><h>');
469 1 done();
470 });
471 });
472
473 1 describe('withBefore', function() {
474 it('should use all of the items in an array before the specified count.', function(done) {
475 var fn = hbs.compile('{{#withBefore array 5}}<{{this}}>{{/withBefore}}');
476 1 expect(fn(context)).to.equal('<a><b><c>');
477 1 done();
478 });
479 });
480
481 1 describe('withFirst', function() {
482 it('should use the first item in an array.', function(done) {
483 var fn = hbs.compile('{{#withFirst array}}{{this}} is smart.{{/withFirst}}');
484 1 expect(fn(context)).to.equal('a is smart.');
485 1 done();
486 });
487 1 it('should return an empty string when no array is passed:', function(done) {
488 expect(hbs.compile('{{#withFirst}}{{/withFirst}}')()).to.equal('');
489 1 done();
490 });
491 1 it('should return an empty string when no array is passed:', function(done) {
492 const customContext = { array: null };
493 1 var fn = hbs.compile('{{#withFirst array}}{{/withFirst}}');
494 1 expect(fn(customContext)).to.equal('');
495 1 done();
496 });
497 1 it('should use the first two items in an array.', function(done) {
498 var fn = hbs.compile('{{#withFirst array 2}}{{this}} is smart.{{/withFirst}}');
499 1 expect(fn(context)).to.equal('a is smart.b is smart.');
500 1 done();
501 });
502 });
503
504 1 describe('withLast', function() {
505 it('should return an empty string when undefined.', function(done) {
506 expect(hbs.compile('{{withLast}}')()).to.equal('');
507 1 done();
508 });
509 1 it('should use the last item in an array.', function(done) {
510 var fn = hbs.compile('{{#withLast array}}{{this}} is dumb.{{/withLast}}');
511 1 expect(fn(context)).to.equal('h is dumb.');
512 1 done();
513 });
514 1 it('should use the last two items in an array.', function(done) {
515 var fn = hbs.compile('{{#withLast array 2}}{{this}} is dumb.{{/withLast}}');
516 1 expect(fn(context)).to.equal('g is dumb.h is dumb.');
517 1 done();
518 });
519 });
520
521 1 describe('withSort', function() {
522 it('should return an empty string when array is undefined', function(done) {
523 var fn = hbs.compile('{{#withSort}}{{this}}{{/withSort}}');
524 1 expect(fn(context)).to.equal('');
525 1 done();
526 });
527
528 1 it('should sort the array in lexicographical order', function(done) {
529 var fn = hbs.compile('{{#withSort array}}{{this}}{{/withSort}}');
530 1 expect(fn(context)).to.equal('abcdefgh');
531 1 done();
532 });
533
534 1 it('should sort the array in reverse order', function(done) {
535 var fn = hbs.compile('{{#withSort array reverse="true"}}{{this}}{{/withSort}}');
536 1 expect(fn(context)).to.equal('hgfedcba');
537 1 done();
538 });
539
540 1 it('should sort the array by deliveries', function(done) {
541 var fn = hbs.compile('{{#withSort collection "deliveries"}}{{name}}: {{deliveries}} <br>{{/withSort}}');
542 1 var res = fn({
543 collection: [
544 {name: 'f', deliveries: 8021 },
545 {name: 'b', deliveries: 239 },
546 {name: 'd', deliveries: -12 }
547 ]
548 });
549 1 expect(res).to.equal('d: -12 <br>b: 239 <br>f: 8021 <br>');
550 1 done();
551 });
552
553 1 it('should sort the array by deliveries in reverse order', function(done) {
554 var fn = hbs.compile('{{#withSort collection "deliveries" reverse="true"}}{{name}}: {{deliveries}} <br>{{/withSort}}');
555 1 var res = fn({
556 collection: [
557 {name: 'f', deliveries: 8021 },
558 {name: 'b', deliveries: 239 },
559 {name: 'd', deliveries: -12 }
560 ]
561 });
562 1 expect(res).to.equal('f: 8021 <br>b: 239 <br>d: -12 <br>');
563 1 done();
564 });
565 });
566 });

spec/helpers/3p/collection.js

100%
114
114
0
Line Lint Hits Source
1 1 const Code = require('code');
2 1 const Lab = require('lab');
3 1 const lab = exports.lab = Lab.script();
4 1 const expect = Code.expect;
5 1 const it = lab.it;
6 1 const describe = lab.describe
7
8 1 const { buildRenderer } = require('../../spec-helpers');
9 1 const renderer = buildRenderer();
10 1 const hbs = renderer.handlebars;
11 1 const helpers = require('../../../helpers/3p/collection');
12
13 1 Object.keys(helpers).forEach(key => {
14 hbs.registerHelper(key, helpers[key]);
15 });
16
17 1 var context = {array: ['a', 'b', 'c', 'd', 'e', 'f', 'g', 'h']};
18
19 1 describe('collection', function() {
20 describe('isEmpty', function() {
21 it('should render the first block when an array is empty.', function(done) {
22 var fn = hbs.compile('{{#isEmpty array}}AAA{{else}}BBB{{/isEmpty}}');
23 1 expect(fn({array: []})).to.equal('AAA');
24 1 done();
25 });
26
27 1 it('should render the first block when the value is null.', function(done) {
28 var fn = hbs.compile('{{#isEmpty}}AAA{{else}}BBB{{/isEmpty}}');
29 1 expect(fn({array: []})).to.equal('AAA');
30 1 done();
31 });
32
33 1 it('should render the second block when an array is not empty.', function(done) {
34 var fn = hbs.compile('{{#isEmpty array}}AAA{{else}}BBB{{/isEmpty}}');
35 1 expect(fn(context)).to.equal('BBB');
36 1 done();
37 });
38
39 1 it('should render the second block when an object is not empty.', function(done) {
40 var fn = hbs.compile('{{#isEmpty object}}AAA{{else}}BBB{{/isEmpty}}');
41 1 expect(fn({object: {foo: 'bar'}})).to.equal('BBB');
42 1 done();
43 });
44
45 1 it('should render the first block when an object is empty.', function(done) {
46 var fn = hbs.compile('{{#isEmpty object}}AAA{{else}}BBB{{/isEmpty}}');
47 1 expect(fn({object: {}})).to.equal('AAA');
48 1 done();
49 });
50 });
51
52 1 describe('iterate', function() {
53 describe('object', function() {
54 it('should iterate over a plain object:', function(done) {
55 var obj = {a: 'aaa', b: 'bbb', c: 'ccc'};
56
57 1 var fn = hbs.compile('{{#iterate obj}}{{.}}{{/iterate}}');
58 1 expect(fn({obj: obj})).to.equal('aaabbbccc');
59 1 done();
60 });
61
62 1 it('should expose `@key`:', function(done) {
63 var obj = {a: 'aaa', b: 'bbb', c: 'ccc'};
64
65 1 var fn = hbs.compile('{{#iterate obj}}{{@key}}{{/iterate}}');
66 1 expect(fn({obj: obj})).to.equal('abc');
67 1 done();
68 });
69
70 1 it('should render the inverse block when falsey:', function(done) {
71 var fn = hbs.compile('{{#iterate obj}}A{{else}}B{{/iterate}}');
72 1 expect(fn()).to.equal('B');
73 1 done();
74 });
75 });
76
77 1 describe('array', function() {
78 it('should iterate over an array:', function(done) {
79 var arr = [{name: 'a'}, {name: 'b'}, {name: 'c'}];
80
81 1 var fn = hbs.compile('{{#iterate arr}}{{name}}{{/iterate}}');
82 1 expect(fn({arr: arr})).to.equal('abc');
83 1 done();
84 });
85
86 1 it('should expose `@index`:', function(done) {
87 var arr = [{name: 'a'}, {name: 'b'}, {name: 'c'}];
88
89 1 var fn = hbs.compile('{{#iterate arr}}{{@index}}{{/iterate}}');
90 1 expect(fn({arr: arr})).to.equal('012');
91 1 done();
92 });
93 });
94 });
95
96 1 describe('length', function() {
97 it('should return the length of the array', function(done) {
98 var fn = hbs.compile('{{length array}}');
99 1 expect(fn(context)).to.equal('8');
100 1 done();
101 });
102
103 1 it('should return an empty string when undefined.', function(done) {
104 expect(hbs.compile('{{length}}')()).to.equal('');
105 1 done();
106 });
107
108 1 it('should return empty strig when the value is null.', function(done) {
109 var fn = hbs.compile('{{length foo}}');
110 1 expect(fn({foo: null})).to.equal('');
111 1 done();
112 });
113
114 1 it('should return the length of a string.', function(done) {
115 var fn = hbs.compile('{{length "foo"}}');
116 1 expect(fn(context)).to.equal('3');
117 1 done();
118 });
119
120 1 it('should parse an array passed as a string', function(done) {
121 var fn = hbs.compile('{{length \'["b", "c", "a"]\'}}');
122 1 expect(fn(context)).to.equal('3');
123 1 done();
124 });
125
126 1 it('should return 0 when the array is invalid:', function(done) {
127 var fn = hbs.compile('{{length \'["b", "c", "a"\'}}');
128 1 expect(fn(context)).to.equal('0');
129 1 done();
130 });
131
132 1 it('should return 0 when the value is not an array:', function(done) {
133 var fn = hbs.compile('{{length foo}}');
134 1 expect(fn({foo: {}})).to.equal('0');
135 1 done();
136 });
137 });
138 });

spec/helpers/3p/comparison.js

100%
380
380
0
Line Lint Hits Source
1 1 const Code = require('code');
2 1 const Lab = require('lab');
3 1 const lab = exports.lab = Lab.script();
4 1 const expect = Code.expect;
5 1 const it = lab.it;
6 1 const describe = lab.describe
7
8 1 const { buildRenderer } = require('../../spec-helpers');
9 1 const renderer = buildRenderer();
10 1 const hbs = renderer.handlebars;
11 1 const helpers = require('../../../helpers/3p/comparison');
12
13 1 Object.keys(helpers).forEach(key => {
14 hbs.registerHelper(key, helpers[key]);
15 });
16
17 1 describe('comparison', function() {
18 describe('and', function() {
19 it('should render a block if both values are truthy.', function(done) {
20 var fn = hbs.compile('{{#and great magnificent}}A{{else}}B{{/and}}');
21 1 expect(fn({great: true, magnificent: true})).to.equal('A');
22 1 done();
23 });
24
25 1 it('should render the inverse block if both values are not truthy.', function(done) {
26 var fn = hbs.compile('{{#and great magnificent}}A{{else}}B{{/and}}');
27 1 expect(fn({great: true, magnificent: false})).to.equal('B');
28 1 done();
29 });
30 });
31
32 1 describe('gt', function() {
33 var fn = hbs.compile('{{#gt a b}}A{{else}}B{{/gt}}');
34
35 1 describe('second arg', function() {
36 it('should render the first block if true.', function(done) {
37 expect(fn({a: 20, b: 15})).to.equal('A');
38 1 done();
39 });
40 1 it('should render the second block if equal.', function(done) {
41 expect(fn({a: 15, b: 15})).to.equal('B');
42 1 done();
43 });
44 1 it('should render the second block if false.', function(done) {
45 expect(fn({a: 14, b: 15})).to.equal('B');
46 1 done();
47 });
48 });
49
50 1 describe('compare hash', function() {
51 it('should not render a block if the value is not equal to a given number.', function(done) {
52 var fn = hbs.compile('{{#gt number compare=8}}A{{/gt}}');
53 1 expect(fn({number: 5})).to.equal('');
54 1 done();
55 });
56 1 it('should render a block if the value is greater than a given number.', function(done) {
57 var fn = hbs.compile('{{#gt number compare=8}}A{{/gt}}');
58 1 expect(fn({number: 10})).to.equal('A');
59 1 done();
60 });
61 1 it('should not render a block if the value is less than a given number.', function(done) {
62 var fn = hbs.compile('{{#gt number compare=8}}A{{/gt}}');
63 1 expect(fn({number: 5})).to.equal('');
64 1 done();
65 });
66 });
67 });
68
69 1 describe('gte', function() {
70 describe('second argument', function() {
71 var fn = hbs.compile('{{#gte a b}}A{{else}}B{{/gte}}');
72
73 1 it('should render the first block if true.', function(done) {
74 expect(fn({a: 20, b: 15})).to.equal('A');
75 1 done();
76 });
77 1 it('should render the first block if equal.', function(done) {
78 expect(fn({a: 15, b: 15})).to.equal('A');
79 1 done();
80 });
81 1 it('should render the second block if false.', function(done) {
82 expect(fn({a: 14, b: 15})).to.equal('B');
83 1 done();
84 });
85 });
86
87 1 describe('hash compare', function() {
88 it('should render a block if the value is greater than a given number.', function(done) {
89 var fn = hbs.compile('{{#gte number compare=8}}A{{/gte}}');
90 1 expect(fn({number: 12})).to.equal('A');
91 1 done();
92 });
93 1 it('should render a block if the value is equal to a given number.', function(done) {
94 var fn = hbs.compile('{{#gte number compare=8}}A{{/gte}}');
95 1 expect(fn({number: 8})).to.equal('A');
96 1 done();
97 });
98 1 it('should not render a block if the value is less than a given number.', function(done) {
99 var fn = hbs.compile('{{#gte number compare=8}}A{{/gte}}');
100 1 expect(fn({number: 5})).to.equal('');
101 1 done();
102 });
103 });
104 });
105
106 1 describe('has', function() {
107 it('should render a block if the condition is true.', function(done) {
108 var fn = hbs.compile('{{#has context "C"}}A{{else}}B{{/has}}');
109 1 expect(fn({context: 'CCC'})).to.equal('A');
110 1 done();
111 });
112
113 1 it('should render the inverse block if false.', function(done) {
114 var fn = hbs.compile('{{#has context "zzz"}}A{{else}}B{{/has}}');
115 1 expect(fn({context: 'CCC'})).to.equal('B');
116 1 done();
117 });
118
119 1 it('should render the inverse block if value is undefined.', function(done) {
120 var fn = hbs.compile('{{#has context}}A{{else}}B{{/has}}');
121 1 expect(fn({context: 'CCC'})).to.equal('B');
122 1 done();
123 });
124
125 1 it('should render the inverse block if context is undefined.', function(done) {
126 var fn = hbs.compile('{{#has}}A{{else}}B{{/has}}');
127 1 expect(fn({context: 'CCC'})).to.equal('B');
128 1 done();
129 });
130
131 1 it('should work with arrays', function(done) {
132 var fn = hbs.compile('{{#has array "a"}}A{{else}}B{{/has}}');
133 1 expect(fn({array: ['a', 'b', 'c']})).to.equal('A');
134 1 done();
135 });
136
137 1 it('should work with two strings', function(done) {
138 var fn = hbs.compile('{{#has "abc" "a"}}A{{else}}B{{/has}}');
139 1 expect(fn()).to.equal('A');
140 1 done();
141 });
142
143 1 it('should return the inverse when the second string is not found', function(done) {
144 var fn = hbs.compile('{{#has "abc" "z"}}A{{else}}B{{/has}}');
145 1 expect(fn()).to.equal('B');
146 1 done();
147 });
148
149 1 it('should work with object keys', function(done) {
150 var fn = hbs.compile('{{#has object "a"}}A{{else}}B{{/has}}');
151 1 expect(fn({object: {a: 'b'}})).to.equal('A');
152 1 done();
153 });
154 });
155
156 1 describe('eq', function() {
157 it('should render a block if the value is equal to a given number.', function(done) {
158 var fn = hbs.compile('{{#eq number compare=8}}A{{/eq}}');
159 1 expect(fn({number: 8})).to.equal('A');
160 1 done();
161 });
162
163 1 it('should render the inverse block if falsey.', function(done) {
164 var fn = hbs.compile('{{#eq number compare=8}}A{{else}}B{{/eq}}');
165 1 expect(fn({number: 9})).to.equal('B');
166 1 done();
167 });
168
169 1 it('should compare first and second args', function(done) {
170 var fn = hbs.compile('{{#eq number 8}}A{{else}}B{{/eq}}');
171 1 expect(fn({number: 9})).to.equal('B');
172 1 done();
173 });
174 });
175
176 1 describe('ifEven', function() {
177 it('should render the block if the given value is an even number', function(done) {
178 var fn = hbs.compile('{{#ifEven number}}A{{else}}B{{/ifEven}}');
179 1 expect(fn({number: 8})).to.equal('A');
180 1 done();
181 });
182
183 1 it('should render the inverse block if the number is odd', function(done) {
184 var fn = hbs.compile('{{#ifEven number}}A{{else}}B{{/ifEven}}');
185 1 expect(fn({number: 9})).to.equal('B');
186 1 done();
187 });
188 });
189
190 1 describe('ifNth', function() {
191 it('should render a custom class on even rows', function(done) {
192 var source = '{{#each items}}<div{{#ifNth 2 @index}}{{else}} class="row-alternate"{{/ifNth}}>{{name}}</div>{{/each}}';
193 1 var fn = hbs.compile(source);
194 1 var context = {
195 items: [
196 { name: 'Philip J. Fry' },
197 { name: 'Turanga Leela' },
198 { name: 'Bender Bending Rodriguez' },
199 { name: 'Amy Wong' },
200 { name: 'Hermes Conrad' }
201 ]
202 };
203 1 expect(fn(context), [
204 '<div>Philip J. Fry</div>',
205 '<div class="row-alternate">Turanga Leela</div>',
206 '<div>Bender Bending Rodriguez</div>',
207 '<div class="row-alternate">Amy Wong</div>',
208 '<div>Hermes Conrad</div>'
209 ].join(''));
210 1 done();
211 });
212 });
213
214 1 describe('ifOdd', function() {
215 it('should render the block if the given value is an even number', function(done) {
216 var fn = hbs.compile('{{#ifOdd number}}A{{else}}B{{/ifOdd}}');
217 1 expect(fn({number: 9})).to.equal('A');
218 1 done();
219 });
220
221 1 it('should render the inverse block if the number is odd', function(done) {
222 var fn = hbs.compile('{{#ifOdd number}}A{{else}}B{{/ifOdd}}');
223 1 expect(fn({number: 8})).to.equal('B')
224 1 done();
225 });
226 });
227
228 1 describe('is', function() {
229 it('should render a block if the condition is true.', function(done) {
230 var fn = hbs.compile('{{#is value "CCC"}}A{{else}}B{{/is}}');
231 1 expect(fn({value: 'CCC'})).to.equal('A');
232 1 done();
233 });
234
235 1 it('should use the `compare` arg on the options hash', function(done) {
236 var fn = hbs.compile('{{#is value compare="CCC"}}A{{else}}B{{/is}}');
237 1 expect(fn({value: 'CCC'})).to.equal('A');
238 1 done();
239 });
240
241 1 it('should render the inverse if the condition is false', function(done) {
242 var fn = hbs.compile('{{#is value "FOO"}}A{{else}}B{{/is}}');
243 1 expect(fn({value: 'CCC'})).to.equal('B');
244 1 done();
245 });
246 });
247
248 1 describe('isnt', function() {
249 it('should render a block if the condition is not true.', function(done) {
250 var fn = hbs.compile('{{#isnt number 2}}A{{else}}B{{/isnt}}');
251 1 expect(fn({number: 3})).to.equal('A');
252 1 done();
253 });
254
255 1 it('should use the `compare` arg on the options hash', function(done) {
256 var fn = hbs.compile('{{#isnt value compare="CCC"}}A{{else}}B{{/isnt}}');
257 1 expect(fn({value: 'CCC'})).to.equal('B');
258 1 done();
259 });
260
261 1 it('should render the inverse if the condition is false', function(done) {
262 var fn = hbs.compile('{{#isnt value "FOO"}}A{{else}}B{{/isnt}}');
263 1 expect(fn({value: 'CCC'})).to.equal('A');
264 1 done();
265 });
266 });
267
268 1 describe('lt', function() {
269 describe('second arg', function() {
270 var fn = hbs.compile('{{#lt a b}}A{{else}}B{{/lt}}');
271
272 1 it('should render the first block if true.', function(done) {
273 expect(fn({a: 14, b: 15})).to.equal('A');
274 1 done();
275 });
276 1 it('should render the second block if equal.', function(done) {
277 expect(fn({a: 15, b: 15})).to.equal('B');
278 1 done();
279 });
280 1 it('should render the second block if false.', function(done) {
281 expect(fn({a: 20, b: 15})).to.equal('B');
282 1 done();
283 });
284 });
285
286 1 describe('compare hash', function() {
287 it('should render a block if the value is less than a given number.', function(done) {
288 var fn = hbs.compile('{{#lt number compare=8}}A{{/lt}}');
289 1 expect(fn({number: 5})).to.equal('A');
290 1 done();
291 });
292 1 it('should not render a block if the value is greater than a given number.', function(done) {
293 var fn = hbs.compile('{{#lt number compare=8}}A{{/lt}}');
294 1 expect(fn({number: 42})).to.equal('');
295 1 done();
296 });
297 });
298 });
299
300 1 describe('lte', function() {
301 var fn = hbs.compile('{{#lte a b}}A{{else}}B{{/lte}}');
302
303 1 describe('second arg', function() {
304 it('should render the first block if true.', function(done) {
305 expect(fn({a: 14, b: 15})).to.equal('A');
306 1 done();
307 });
308
309 1 it('should render the first block if equal.', function(done) {
310 expect(fn({a: 15, b: 15})).to.equal('A');
311 1 done();
312 });
313
314 1 it('should render the second block if false.', function(done) {
315 expect(fn({a: 20, b: 15})).to.equal('B');
316 1 done();
317 });
318 });
319
320 1 describe('compare hash', function() {
321 it('should render a block if the value is less than a given number.', function(done) {
322 var fn = hbs.compile('{{#lte number compare=8}}A{{/lte}}');
323 1 expect(fn({number: 1})).to.equal('A');
324 1 done();
325 });
326
327 1 it('should render a block if the value is equal to a given number.', function(done) {
328 var fn = hbs.compile('{{#lte number compare=8}}A{{/lte}}');
329 1 expect(fn({number: 8})).to.equal('A');
330 1 done();
331 });
332
333 1 it('should not render a block if the value is greater than a given number.', function(done) {
334 var fn = hbs.compile('{{#lte number compare=8}}A{{/lte}}');
335 1 expect(fn({number: 27})).to.equal('');
336 1 done();
337 });
338 });
339 });
340
341 1 describe('neither', function() {
342 it('should render a block if one of the values is truthy.', function(done) {
343 var fn = hbs.compile('{{#neither great magnificent}}A{{else}}B{{/neither}}');
344 1 expect(fn({great: false, magnificent: false})).to.equal('A');
345 1 done();
346 });
347
348 1 it('should render the inverse block if neither are true.', function(done) {
349 var fn = hbs.compile('{{#neither great magnificent}}A{{else}}B{{/neither}}');
350 1 expect(fn({great: true, magnificent: false})).to.equal('B');
351 1 done();
352 });
353 });
354
355 1 describe('unlessEq', function() {
356 it('should render a block unless the value is equal to a given number.', function(done) {
357 var fn = hbs.compile('{{#unlessEq number compare=8}}A{{/unlessEq}}');
358 1 expect(fn({number: 10})).to.equal('A');
359 1 done();
360 });
361 1 it('should render a block unless the value is equal to a given number.', function(done) {
362 var fn = hbs.compile('{{#unlessEq number compare=8}}A{{/unlessEq}}');
363 1 expect(fn({number: 8})).to.equal('');
364 1 done();
365 });
366 });
367
368 1 describe('unlessGt', function() {
369 it('should render a block unless the value is greater than a given number.', function(done) {
370 var fn = hbs.compile('{{#unlessGt number compare=8}}A{{/unlessGt}}');
371 1 expect(fn({number: 5})).to.equal('A');
372 1 done();
373 });
374 1 it('should render a block unless the value is greater than a given number.', function(done) {
375 var fn = hbs.compile('{{#unlessGt number compare=8}}A{{/unlessGt}}');
376 1 expect(fn({number: 10})).to.equal('');
377 1 done();
378 });
379 });
380
381 1 describe('unlessLt', function() {
382 it('should render a block unless the value is less than a given number.', function(done) {
383 var fn = hbs.compile('{{#unlessLt number compare=8}}A{{/unlessLt}}');
384 1 expect(fn({number: 10})).to.equal('A');
385 1 done();
386 });
387 1 it('should render a block unless the value is less than a given number.', function(done) {
388 var fn = hbs.compile('{{#unlessLt number compare=8}}A{{/unlessLt}}');
389 1 expect(fn({number: 5})).to.equal('');
390 1 done();
391 });
392 });
393
394 1 describe('unlessGteq', function() {
395 it('should render a block unless the value is greater than or equal to a given number.', function(done) {
396 var fn = hbs.compile('{{#unlessGteq number compare=8}}A{{/unlessGteq}}');
397 1 expect(fn({number: 4})).to.equal('A');
398 1 done();
399 });
400 1 it('should render a block unless the value is greater than or equal to a given number.', function(done) {
401 var fn = hbs.compile('{{#unlessGteq number compare=8}}A{{/unlessGteq}}');
402 1 expect(fn({number: 8})).to.equal('');
403 1 done();
404 });
405 1 it('should not render a block unless the value is greater than or equal to a given number.', function(done) {
406 var fn = hbs.compile('{{#unlessGteq number compare=8}}A{{/unlessGteq}}');
407 1 expect(fn({number: 34})).to.equal('');
408 1 done();
409 });
410 });
411
412 1 describe('unlessLteq', function() {
413 it('should render a block unless the value is less than or equal to a given number.', function(done) {
414 var fn = hbs.compile('{{#unlessLteq number compare=8}}A{{/unlessLteq}}');
415 1 expect(fn({number: 10})).to.equal('A');
416 1 done();
417 });
418 1 it('should render a block unless the value is less than or equal to a given number.', function(done) {
419 var fn = hbs.compile('{{#unlessLteq number compare=8}}A{{/unlessLteq}}');
420 1 expect(fn({number: 8})).to.equal('');
421 1 done();
422 });
423 1 it('should not render a block unless the value is less than or equal to a given number.', function(done) {
424 var fn = hbs.compile('{{#unlessLteq number compare=8}}A{{/unlessLteq}}');
425 1 expect(fn({number: 4})).to.equal('');
426 1 done();
427 });
428 });
429 });

spec/helpers/3p/first.js

100%
71
71
0
Line Lint Hits Source
1 1 const Lab = require('lab'),
2 lab = exports.lab = Lab.script(),
3 describe = lab.experiment,
4 it = lab.it;
5 1 const {testRunner} = require("../../spec-helpers");
6
7 1 describe('first', () => {
8 describe('string', () => {
9
10 const context = {
11 smallString: 'abc',
12 empty: '',
13 myString: 'BigCommerce'
14 };
15
16 1 const runner = testRunner({context});
17
18 1 it('should extract the first char when no "n" is given.', done => {
19 runner([{
20 input: '{{first myString}}',
21 output: 'B'
22 }], done);
23 });
24
25 1 it('should return the whole string when "n" is bigger that string size', done => {
26 runner([{
27 input: '{{first smallString 5}}',
28 output: context.smallString
29 }], done);
30 });
31
32 1 it('should return the expected string', done => {
33 runner([{
34 input: '{{first myString 3}}',
35 output: 'Big'
36 }], done);
37 });
38
39 1 it('should return empty string when empty string is provided', done => {
40 runner([{
41 input: '{{first empty 3}}',
42 output: ''
43 }], done);
44 });
45 });
46
47 1 describe('array', () => {
48
49 const context = {
50 small: [1,2,3],
51 empty:[],
52 arrayYay: [1,2,3,4,5,6,7,8,9,0]
53 };
54
55 1 const runner = testRunner({context});
56
57 1 it('should extract the first elem when no "n" is given.', done => {
58 runner([{
59 input: '{{first arrayYay}}',
60 output: '1'
61 }], done);
62 });
63
64 1 it('should return the whole array when "n" is bigger that array size', done => {
65 runner([{
66 input: '{{first small 5}}',
67 output: context.small.join()
68 }], done);
69 });
70
71 1 it('should return the expected elems', done => {
72 runner([{
73 input: '{{first arrayYay 3}}',
74 output: [1,2,3].join()
75 }], done);
76 });
77
78 1 it('should return empty string when empty array is provided', done => {
79 runner([{
80 input: '{{first empty 3}}',
81 output: ''
82 }], done);
83 });
84 });
85 });

spec/helpers/3p/html.js

100%
227
227
0
Line Lint Hits Source
1 1 const Code = require('code');
2 1 const Lab = require('lab');
3 1 const lab = exports.lab = Lab.script();
4 1 const expect = Code.expect;
5 1 const it = lab.it;
6 1 const describe = lab.describe
7
8 1 const { buildRenderer } = require('../../spec-helpers');
9 1 const renderer = buildRenderer();
10 1 const hbs = renderer.handlebars;
11 1 const helpers = require('../../../helpers/3p/html');
12
13 1 Object.keys(helpers).forEach(key => {
14 hbs.registerHelper(key, helpers[key]);
15 });
16
17
18 1 var locals = {data: [{aaa: 'AAA', bbb: 'BBB'}, {aaa: 'CCC', bbb: 'DDD'}]};
19
20 1 describe('html', function() {
21 describe('ellipsis', function() {
22 it('should return an empty string if undefined', function(done) {
23 var fn = hbs.compile('{{ellipsis}}');
24 1 expect(fn()).to.equal('');
25 1 done();
26 });
27 1 it('should return then string truncated by a specified length.', function(done) {
28 var fn = hbs.compile('{{ellipsis "Bender should not be allowed on tv." 31}}');
29 1 expect(fn()).to.equal('Bender should not be allowed on…');
30 1 done();
31 });
32 1 it('should return the string if shorter than the specified length.', function(done) {
33 var fn = hbs.compile('{{ellipsis "Bender should not be allowed on tv." 100}}');
34 1 expect(fn()).to.equal('Bender should not be allowed on tv.');
35 1 done();
36 });
37 });
38
39 1 describe('sanitize', function() {
40 it('should return an empty string when undefined.', function(done) {
41 expect(hbs.compile('{{sanitize}}')()).to.equal('');
42 1 done();
43 });
44 1 it('should strip html from a string.', function(done) {
45 var actual = hbs.compile('{{sanitize "<span>foo</span>"}}')();
46 1 expect(actual).to.equal('foo');
47 1 done();
48 });
49 });
50
51 1 describe('ul', function() {
52 it('should should return an unordered list', function(done) {
53 var fn = hbs.compile('{{#ul data class="names"}}{{aaa}} {{bbb}}{{/ul}}');
54 1 expect(fn(locals)).to.equal('<ul class="names"><li>AAA BBB</li>\n<li>CCC DDD</li></ul>');
55 1 done();
56 });
57 });
58
59 1 describe('ol', function() {
60 it('should should return an ordered list', function(done) {
61 var fn = hbs.compile('{{#ol data class="names"}}{{aaa}} {{bbb}}{{/ol}}');
62 1 expect(fn(locals)).to.equal('<ol class="names"><li>AAA BBB</li>\n<li>CCC DDD</li></ol>');
63 1 done();
64 });
65 });
66
67 1 describe('thumbnailImage', function() {
68 describe('{{{thumbnailImage context}}}', function() {
69 it('should return figure with link and caption', function(done) {
70 var context = {
71 data: {
72 id: 'id',
73 alt: 'Picture of a placeholder',
74 thumbnail: 'http://placehold.it/200x200/0eafff/ffffff.png',
75 size: {
76 width: 200,
77 height: 200
78 },
79 full: 'http://placehold.it/600x400/0eafff/ffffff.png',
80 caption: 'My new caption!'
81 }
82 };
83 1 var fn = hbs.compile('{{{thumbnailImage data}}}');
84 1 var comparison = [
85 '<figure id="image-id">',
86 '<a href="http://placehold.it/600x400/0eafff/ffffff.png" rel="thumbnail">',
87 '<img alt="Picture of a placeholder" src="http://placehold.it/200x200/0eafff/ffffff.png" width="200" height="200">',
88 '</a>',
89 '<figcaption>My new caption!</figcaption>',
90 '</figure>',
91 ].join('\n');
92 1 expect(fn(context)).to.equal(comparison);
93 1 done();
94 });
95
96 1 it('should return figure with extra class "test"', function(done) {
97 var source = '{{{thumbnailImage data}}}';
98 1 var context = {
99 data: {
100 id: 'id',
101 alt: 'Picture of a placeholder',
102 thumbnail: 'http://placehold.it/200x200/0eafff/ffffff.png',
103 size: {
104 width: 200,
105 height: 200
106 },
107 classes: {
108 figure: ['test']
109 },
110 full: 'http://placehold.it/600x400/0eafff/ffffff.png',
111 caption: 'My new caption!'
112 }
113 };
114
115 1 var fn = hbs.compile(source);
116 1 var comparison = [
117 '<figure id="image-id" class="test">',
118 '<a href="http://placehold.it/600x400/0eafff/ffffff.png" rel="thumbnail">',
119 '<img alt="Picture of a placeholder" src="http://placehold.it/200x200/0eafff/ffffff.png" width="200" height="200">',
120 '</a>',
121 '<figcaption>My new caption!</figcaption>',
122 '</figure>'
123 ].join('\n');
124 1 expect(fn(context)).to.equal(comparison);
125 1 done();
126 });
127
128 1 it('should return figure with image that has class "test"', function(done) {
129 var source = '{{{thumbnailImage data}}}';
130 1 var context = {
131 data: {
132 id: 'id',
133 alt: 'Picture of a placeholder',
134 thumbnail: 'http://placehold.it/200x200/0eafff/ffffff.png',
135 size: {
136 width: 200,
137 height: 200
138 },
139 full: 'http://placehold.it/600x400/0eafff/ffffff.png',
140 classes: {
141 image: ['test']
142 },
143 caption: 'My new caption!'
144 }
145 };
146 1 var fn = hbs.compile(source);
147 1 var comparison = [
148 '<figure id="image-id">',
149 '<a href="http://placehold.it/600x400/0eafff/ffffff.png" rel="thumbnail">',
150 '<img alt="Picture of a placeholder" src="http://placehold.it/200x200/0eafff/ffffff.png" width="200" height="200" class="test">',
151 '</a>',
152 '<figcaption>My new caption!</figcaption>',
153 '</figure>'
154 ].join('\n');
155 1 expect(fn(context)).to.equal(comparison);
156 1 done();
157 });
158
159 1 it('should return figure with link that has class "test"', function(done) {
160 var source = '{{{thumbnailImage data}}}';
161 1 var context = {
162 data: {
163 id: 'id',
164 alt: 'Picture of a placeholder',
165 thumbnail: 'http://placehold.it/200x200/0eafff/ffffff.png',
166 size: {
167 width: 200,
168 height: 200
169 },
170 full: 'http://placehold.it/600x400/0eafff/ffffff.png',
171 classes: {
172 link: ['test']
173 },
174 caption: 'My new caption!'
175 }
176 };
177 1 var fn = hbs.compile(source);
178 1 var comparison = [
179 '<figure id="image-id">',
180 '<a href="http://placehold.it/600x400/0eafff/ffffff.png" rel="thumbnail" class="test">',
181 '<img alt="Picture of a placeholder" src="http://placehold.it/200x200/0eafff/ffffff.png" width="200" height="200">',
182 '</a>',
183 '<figcaption>My new caption!</figcaption>',
184 '</figure>',
185 ].join('\n');
186 1 expect(fn(context)).to.equal(comparison);
187 1 done();
188 });
189
190 1 it('should return figure without link', function(done) {
191 var source = '{{{thumbnailImage data}}}';
192 1 var context = {
193 data: {
194 id: 'id',
195 alt: 'Picture of a placeholder',
196 thumbnail: 'http://placehold.it/200x200/0eafff/ffffff.png',
197 size: {
198 width: 200,
199 height: 200
200 },
201 caption: 'My new caption!'
202 }
203 };
204 1 var fn = hbs.compile(source);
205 1 var comparison = [
206 '<figure id="image-id">',
207 '<img alt="Picture of a placeholder" src="http://placehold.it/200x200/0eafff/ffffff.png" width="200" height="200">',
208 '<figcaption>My new caption!</figcaption>',
209 '</figure>'
210 ].join('\n');
211 1 expect(fn(context)).to.equal(comparison);
212 1 done();
213 });
214
215 1 it('should return figure without caption', function(done) {
216 var source = '{{{thumbnailImage data}}}';
217 1 var context = {
218 data: {
219 id: 'id',
220 alt: 'Picture of a placeholder',
221 thumbnail: 'http://placehold.it/200x200/0eafff/ffffff.png',
222 size: {
223 width: 200,
224 height: 200
225 },
226 full: 'http://placehold.it/600x400/0eafff/ffffff.png'
227 }
228 };
229 1 var fn = hbs.compile(source);
230 1 var comparison = [
231 '<figure id="image-id">',
232 '<a href="http://placehold.it/600x400/0eafff/ffffff.png" rel="thumbnail">',
233 '<img alt="Picture of a placeholder" src="http://placehold.it/200x200/0eafff/ffffff.png" width="200" height="200">',
234 '</a>',
235 '</figure>'
236 ].join('\n');
237 1 expect(fn(context)).to.equal(comparison);
238 1 done();
239 });
240 });
241 });
242 });

spec/helpers/3p/inflection.js

100%
38
38
0
Line Lint Hits Source
1 1 const Code = require('code');
2 1 const Lab = require('lab');
3 1 const lab = exports.lab = Lab.script();
4 1 const expect = Code.expect;
5 1 const it = lab.it;
6 1 const describe = lab.describe
7
8 1 const { buildRenderer } = require('../../spec-helpers');
9 1 const renderer = buildRenderer();
10 1 const hbs = renderer.handlebars;
11 1 const helpers = require('../../../helpers/3p/inflection');
12
13 1 Object.keys(helpers).forEach(key => {
14 hbs.registerHelper(key, helpers[key]);
15 });
16
17 1 describe('inflection', function() {
18 describe('inflect', function() {
19 it('should return the plural or singular form of a word based on a value.', function(done) {
20 var template = hbs.compile('{{inflect mail "junk" "mail"}}');
21 1 expect(template({mail: 3})).to.equal('mail');
22 1 done();
23 });
24
25 1 it('should return the plural or singular form of a word based on a value and include the count.', function(done) {
26 var template = hbs.compile('{{inflect messages "message" "messages" true}}');
27 1 expect(template({messages: 1})).to.equal('1 message');
28 1 done();
29 });
30 });
31
32 1 describe('ordinalize', function() {
33 it('should return an ordinalized string.', function(done) {
34 expect(hbs.compile('{{ordinalize 1}}')()).to.equal('1st');
35 1 expect(hbs.compile('{{ordinalize 3}}')()).to.equal('3rd');
36 1 expect(hbs.compile('{{ordinalize 11}}')()).to.equal('11th');
37 1 expect(hbs.compile('{{ordinalize 21}}')()).to.equal('21st');
38 1 expect(hbs.compile('{{ordinalize 29}}')()).to.equal('29th');
39 1 expect(hbs.compile('{{ordinalize 22}}')()).to.equal('22nd');
40 1 done();
41 });
42 });
43 });

spec/helpers/3p/last.js

100%
71
71
0
Line Lint Hits Source
1 1 const Lab = require('lab'),
2 lab = exports.lab = Lab.script(),
3 describe = lab.experiment,
4 it = lab.it;
5 1 const {testRunner} = require("../../spec-helpers");
6
7 1 describe('last', () => {
8 describe('string', () => {
9
10 const context = {
11 smallString: 'abc',
12 empty: '',
13 myString: 'BigCommerce'
14 };
15
16 1 const runner = testRunner({context});
17
18 1 it('should extract the last char when no "n" is given.', done => {
19 runner([{
20 input: '{{last myString}}',
21 output: 'e'
22 }], done);
23 });
24
25 1 it('should return the whole string when "n" is bigger that string size', done => {
26 runner([{
27 input: '{{last smallString 5}}',
28 output: context.smallString
29 }], done);
30 });
31
32 1 it('should return the expected string', done => {
33 runner([{
34 input: '{{last myString 3}}',
35 output: 'rce'
36 }], done);
37 });
38
39 1 it('should return empty string when empty string is provided', done => {
40 runner([{
41 input: '{{last empty 3}}',
42 output: ''
43 }], done);
44 });
45 });
46
47 1 describe('array', () => {
48
49 const context = {
50 small: [1, 2, 3],
51 empty: [],
52 arrayYay: [1, 2, 3, 4, 5, 6, 7, 8, 9, 0]
53 };
54
55 1 const runner = testRunner({context});
56
57 1 it('should extract the last elem when no "n" is given.', done => {
58 runner([{
59 input: '{{last arrayYay}}',
60 output: '0'
61 }], done);
62 });
63
64 1 it('should return the whole array when "n" is bigger that array size', done => {
65 runner([{
66 input: '{{last small 5}}',
67 output: context.small.join()
68 }], done);
69 });
70
71 1 it('should return the expected elems', done => {
72 runner([{
73 input: '{{last arrayYay 3}}',
74 output: [8, 9, 0].join()
75 }], done);
76 });
77
78 1 it('should return empty string when empty array is provided', done => {
79 runner([{
80 input: '{{last empty 3}}',
81 output: ''
82 }], done);
83 });
84 });
85 });

spec/helpers/3p/markdown.js

100%
22
22
0
Line Lint Hits Source
1 1 const Code = require('code');
2 1 const Lab = require('lab');
3 1 const lab = exports.lab = Lab.script();
4 1 const expect = Code.expect;
5 1 const it = lab.it;
6 1 const describe = lab.describe
7
8 1 const { buildRenderer } = require('../../spec-helpers');
9 1 const renderer = buildRenderer();
10 1 const hbs = renderer.handlebars;
11 1 const helpers = require('../../../helpers/3p/markdown');
12
13 1 Object.keys(helpers).forEach(key => {
14 hbs.registerHelper(key, helpers[key]);
15 });
16
17
18 1 describe('markdown', function() {
19 describe('markdown', function() {
20 it('should render markdown using the {{#markdown}} block helper', function(done) {
21 var template = hbs.compile('{{#markdown}}## {{../title}}{{/markdown}}');
22 1 expect(template({title: 'Markdown Test'})).to.equal('<h2>Markdown Test</h2>\n');
23 1 done();
24 });
25 });
26 });
27

spec/helpers/3p/math.js

100%
98
98
0
Line Lint Hits Source
1 1 const Code = require('code');
2 1 const Lab = require('lab');
3 1 const lab = exports.lab = Lab.script();
4 1 const expect = Code.expect;
5 1 const it = lab.it;
6 1 const describe = lab.describe
7
8 1 const { buildRenderer } = require('../../spec-helpers');
9 1 const renderer = buildRenderer();
10 1 const hbs = renderer.handlebars;
11 1 const helpers = require('../../../helpers/3p/math');
12
13 1 Object.keys(helpers).forEach(key => {
14 hbs.registerHelper(key, helpers[key]);
15 });
16 1 describe('math', function() {
17 describe('add', function() {
18 it('should return the sum of two numbers.', function(done) {
19 var fn = hbs.compile('{{add value 5}}');
20 1 expect(fn({value: 5})).to.equal('10');
21 1 done();
22 });
23 });
24
25 1 describe('average', function() {
26 it('should return the average of a list of numbers:', function(done) {
27 var fn = hbs.compile('{{avg 1 2 3 4}}');
28 1 expect(fn()).to.equal('2.5');
29 1 done();
30 });
31
32 1 it('should return the average of an array of numbers:', function(done) {
33 var fn = hbs.compile('{{avg array}}');
34 1 expect(fn({array: [1, 3, 6, 9]})).to.equal('4.75');
35 1 done();
36 });
37 });
38
39 1 describe('ceil', function() {
40 it('should return the value rounded up to the nearest integer.', function(done) {
41 var fn = hbs.compile('{{ceil value}}');
42 1 expect(fn({value: 5.6})).to.equal('6');
43 1 done();
44 });
45 });
46
47 1 describe('divide', function() {
48 it('should return the division of two numbers.', function(done) {
49 var fn = hbs.compile('{{divide value 5}}');
50 1 expect(fn({value: 5})).to.equal('1');
51 1 done();
52 });
53 });
54
55 1 describe('floor', function() {
56 it('should return the value rounded down to the nearest integer.', function(done) {
57 var fn = hbs.compile('{{floor value}}');
58 1 expect(fn({value: 5.6})).to.equal('5');
59 1 done();
60 });
61 });
62
63 1 describe('multiply', function() {
64 it('should return the multiplication of two numbers.', function(done) {
65 var fn = hbs.compile('{{multiply value 5}}');
66 1 expect(fn({value: 5})).to.equal('25');
67 1 done();
68 });
69 });
70
71 1 describe('round', function() {
72 it('should return the value rounded to the nearest integer.', function(done) {
73 var fn = hbs.compile('{{round value}}');
74 1 expect(fn({value: 5.69})).to.equal('6');
75 1 done();
76 });
77 });
78
79 1 describe('subtract', function() {
80 it('should return the difference of two numbers.', function(done) {
81 var fn = hbs.compile('{{subtract value 5}}');
82 1 expect(fn({value: 5})).to.equal('0');
83 1 done();
84 });
85 });
86
87 1 describe('sum', function() {
88 it('should return the sum of multiple numbers.', function(done) {
89 var fn = hbs.compile('{{sum value 67 80}}');
90 1 expect(fn({value: 20})).to.equal('167');
91 1 done();
92 });
93 1 it('should return the sum of multiple numbers.', function(done) {
94 var fn = hbs.compile('{{sum 1 2 3}}');
95 1 expect(fn()).to.equal('6');
96 1 done();
97 });
98 1 it('should return the total sum of array.', function(done) {
99 var fn = hbs.compile('{{sum value}}');
100 1 expect(fn({value: [1, 2, 3]})).to.equal('6');
101 1 done();
102 });
103 1 it('should return the total sum of array and numbers.', function(done) {
104 var fn = hbs.compile('{{sum value 5}}');
105 1 expect(fn({value: [1, 2, 3]})).to.equal('11');
106 1 done();
107 });
108 });
109 });

spec/helpers/3p/misc.js

100%
67
67
0
Line Lint Hits Source
1 1 const Code = require('code');
2 1 const Lab = require('lab');
3 1 const lab = exports.lab = Lab.script();
4 1 const expect = Code.expect;
5 1 const it = lab.it;
6 1 const describe = lab.describe
7
8 1 const { buildRenderer } = require('../../spec-helpers');
9 1 const renderer = buildRenderer();
10 1 const hbs = renderer.handlebars;
11 1 const helpers = require('../../../helpers/3p/misc');
12
13 1 Object.keys(helpers).forEach(key => {
14 hbs.registerHelper(key, helpers[key]);
15 });
16
17
18 1 describe('misc', function() {
19 describe('default', function() {
20 it('should use the given value:', function(done) {
21 expect(hbs.compile('{{default title "A"}}')({title: 'B'})).to.equal('B');
22 1 done();
23 });
24 1 it('should fallback to the default value when no value exists', function(done) {
25 expect(hbs.compile('{{default title "A"}}')({title: null})).to.equal('A');
26 1 expect(hbs.compile('{{default title "A"}}')()).to.equal('A');
27 1 done();
28 });
29 });
30
31 1 describe('noop', function() {
32 it('should be a noop', function(done) {
33 var fn = hbs.compile('{{#noop}}{{message}}{{/noop}}');
34 1 expect(fn({message: 'This is a test'})).to.equal('This is a test');
35 1 done();
36 });
37 });
38
39 1 describe('withHash', function() {
40 it('should return an empty sting', function(done) {
41 var fn = hbs.compile('{{#withHash}}{{message}}{{/withHash}}');
42 1 var actual = fn({message: 'This is a test'});
43 1 expect(typeof actual).to.equal('string');
44 1 expect(actual).to.equal('');
45 1 done();
46 });
47 1 it('should not blow up when no hash is defined.', function(done) {
48 var fn = hbs.compile('{{#withHash}}{{/withHash}}');
49 1 expect(fn()).to.equal('');
50 1 done();
51 });
52 1 it('should return the inverse hash when defined and the value is falsy.', function(done) {
53 var fn = hbs.compile('{{#withHash}}foo{{else}}bar{{/withHash}}');
54 1 expect(fn()).to.equal('bar');
55 1 done();
56 });
57 1 it('should return string from the newly created context', function(done) {
58 var fn = hbs.compile('{{#withHash message="test"}}{{message}}{{/withHash}}');
59 1 expect(fn({message: 'This is a test'})).to.equal('test');
60 1 done();
61 });
62 1 it('should return string from the parent context', function(done) {
63 var fn = hbs.compile('{{#withHash message=this.message}}{{message}}{{/withHash}}');
64 1 expect(fn({message: 'This is a test'})).to.equal('This is a test');
65 1 done();
66 });
67 1 it('should add two attributes to the new context', function(done) {
68 var fn = hbs.compile('{{#withHash subject="Feedback" message="Hello!"}}{{subject}} - {{message}}{{/withHash}}');
69 1 expect(fn({})).to.equal('Feedback - Hello!');
70 1 done();
71 });
72 });
73 });

spec/helpers/3p/number.js

100%
107
107
0
Line Lint Hits Source
1 1 const Code = require('code');
2 1 const Lab = require('lab');
3 1 const lab = exports.lab = Lab.script();
4 1 const expect = Code.expect;
5 1 const it = lab.it;
6 1 const describe = lab.describe
7
8 1 const { buildRenderer } = require('../../spec-helpers');
9 1 const renderer = buildRenderer();
10 1 const hbs = renderer.handlebars;
11
12
13 1 describe('number', function() {
14 describe('phoneNumber', function() {
15 it('Format a phone number.', function(done) {
16 var fn = hbs.compile('{{phoneNumber value}}');
17 1 expect(fn({value: '8005551212'})).to.equal('(800) 555-1212');
18 1 done();
19 });
20 });
21
22 1 describe('toFixed', function() {
23 it('should return the value rounded to the nearest integer.', function(done) {
24 var fn = hbs.compile('{{toFixed value}}');
25 1 expect(fn({value: 5.53231 })).to.equal('6');
26 1 done();
27 });
28 1 it('should return the value rounded exactly n digits after the decimal place.', function(done) {
29 var fn = hbs.compile('{{toFixed value 3}}');
30 1 expect(fn({value: 5.53231 })).to.equal('5.532');
31 1 done();
32 });
33 });
34
35 1 describe('toPrecision', function() {
36 it('Returns the number in fixed-point or exponential notation rounded to n significant digits.', function(done) {
37 var fn = hbs.compile('{{toPrecision value}}');
38 1 expect(fn({value: 555.322 })).to.equal('6e+2');
39 1 done();
40 });
41 1 it('should return the value rounded exactly n digits after the decimal place.', function(done) {
42 var fn = hbs.compile('{{toPrecision value 4}}');
43 1 expect(fn({value: 555.322 })).to.equal('555.3');
44 1 done();
45 });
46 });
47
48 1 describe('toExponential', function() {
49 it('should return the number in fixed-point or exponential notation rounded to n significant digits.', function(done) {
50 var fn = hbs.compile('{{toExponential value}}');
51 1 expect(fn({value: 5 })).to.equal('5e+0');
52 1 done();
53 });
54 1 it('should return the number in fixed-point or exponential notation rounded to exactly n significant digits.', function(done) {
55 var fn = hbs.compile('{{toExponential value 5}}');
56 1 expect(fn({value: 5 })).to.equal('5.00000e+0');
57 1 done();
58 });
59 });
60
61 1 describe('toInt', function() {
62 it('should return an integer.', function(done) {
63 var fn = hbs.compile('{{toInt value}}');
64 1 expect(fn({value: '3cc'})).to.equal('3');
65 1 done();
66 });
67 });
68
69 1 describe('toFloat', function() {
70 it('should return a floating point number.', function(done) {
71 var fn = hbs.compile('{{toFloat value}}');
72 1 expect(fn({value: '3.1cc'})).to.equal('3.1');
73 1 done();
74 });
75 });
76
77 1 describe('addCommas', function() {
78 it('should add commas to a number.', function(done) {
79 var fn = hbs.compile('{{addCommas value}}');
80 1 expect(fn({value: 2222222 })).to.equal('2,222,222');
81 1 done();
82 });
83 });
84
85 1 describe('toAbbr', function() {
86 it('should abbreviate the given number.', function(done) {
87 var fn = hbs.compile('{{toAbbr number}}');
88 1 expect(fn({number: 123456789 })).to.equal('123.46m');
89 1 done();
90 });
91
92 1 it('should abbreviate a number with to the given decimal.', function(done) {
93 var fn = hbs.compile('{{toAbbr number 3}}');
94 1 expect(fn({number: 123456789 })).to.equal('123.457m');
95 1 done();
96 });
97
98 1 it('should round up to the next increment', function(done) {
99 var fn = hbs.compile('{{toAbbr number}}');
100 1 expect(fn({number: 999 })).to.equal('1k');
101 1 done();
102 });
103
104 1 it('should abbreviate a number based on a number and include decimal.', function(done) {
105 expect(hbs.compile('{{toAbbr number 0}}')({number: 9999999 })).to.equal('10m');
106 1 expect(hbs.compile('{{toAbbr number}}')({number: 1000000000 })).to.equal('1b');
107 1 expect(hbs.compile('{{toAbbr number}}')({number: 1000000000000 })).to.equal('1t');
108 1 expect(hbs.compile('{{toAbbr number}}')({number: 1000000000000000 })).to.equal('1q');
109 1 expect(hbs.compile('{{toAbbr number}}')({number: 99393999393 })).to.equal('99.39b');
110 1 done();
111 });
112 });
113
114 1 describe('random', function() {
115 it('should return a random number between two values.', function(done) {
116 var fn = hbs.compile('{{random 5 10}}');
117 1 expect(fn()).to.be.within(5,10);
118 1 done();
119 });
120 });
121 });

spec/helpers/3p/object.js

100%
146
146
0
Line Lint Hits Source
1 1 const Code = require('code');
2 1 const Lab = require('lab');
3 1 const lab = exports.lab = Lab.script();
4 1 const expect = Code.expect;
5 1 const it = lab.it;
6 1 const describe = lab.describe
7
8 1 const { buildRenderer } = require('../../spec-helpers');
9 1 const renderer = buildRenderer();
10 1 const hbs = renderer.handlebars;
11 1 const helpers = require('../../../helpers/3p/object');
12
13 1 Object.keys(helpers).forEach(key => {
14 hbs.registerHelper(key, helpers[key]);
15 });
16
17
18 1 var context = {object: {a: 'b', c: 'd', e: 'f'}};
19
20 1 describe('object', function() {
21 describe('extend', function() {
22 it('should extend multiple objects into one:', function(done) {
23 var fn = hbs.compile('{{{stringify (extend a d g)}}}');
24 1 var actual = fn({a: {b: 'c'}, d: {e: 'f'}, g: {h: 'i'}});
25 1 expect(actual).to.equal('{"b":"c","e":"f","h":"i"}');
26 1 done();
27 });
28
29 1 it('should work as a non-helper util:', function(done) {
30 var actual = helpers.extend({a: {b: 'c'}}, {d: {e: 'f'}}, {g: {h: 'i'}});
31 1 expect(actual).to.equal({ a: { b: 'c' }, d: { e: 'f' }, g: { h: 'i' } });
32 1 done();
33 });
34
35 1 it('should skip over sparse objects', function(done) {
36 var actual = helpers.extend({a: {b: 'c'}}, null, {g: {h: 'i'}});
37 1 expect(actual).to.equal({ a: { b: 'c' }, g: { h: 'i' } });
38 1 done();
39 });
40 });
41
42 1 describe('forIn', function() {
43 it('should iterate over each property in an object:', function(done) {
44 var fn = hbs.compile('{{#forIn this}} {{@key}} {{.}} {{/forIn}}');
45 1 expect(fn(context.object)).to.equal(' a b c d e f ');
46 1 done();
47 });
48
49 1 it('should return the inverse block if no object is passed:', function(done) {
50 var fn = hbs.compile('{{#forIn}} {{.}} {{else}} Nada. {{/forIn}}');
51 1 expect(fn(context.object)).to.equal(' Nada. ');
52 1 done();
53 });
54
55 1 it('should expose private variables:', function(done) {
56 var fn = hbs.compile('{{#forIn this abc=object}} {{@abc.a}} {{/forIn}}');
57 1 expect(fn(context)).to.equal(' b ');
58 1 done();
59 });
60 });
61
62 1 describe('forOwn', function() {
63 it('should iterate over each property in an object:', function(done) {
64 var fn = hbs.compile('{{#forOwn this}} {{@key}} {{.}} {{/forOwn}}');
65 1 expect(fn(context.object)).to.equal(' a b c d e f ');
66 1 done();
67 });
68
69 1 it('should return the inverse block if no object is passed:', function(done) {
70 var fn = hbs.compile('{{#forOwn}} {{.}} {{else}} Nada. {{/forOwn}}');
71 1 expect(fn(context.object)).to.equal(' Nada. ');
72 1 done();
73 });
74
75 1 it('should only expose "own" keys:', function(done) {
76 function Foo() {
77 1 this.a = 'b';
78 1 this.b = 'c';
79 }
80 1 Foo.prototype.c = 'd';
81 1 var fn = hbs.compile('{{#forOwn this}} {{.}} {{/forOwn}}');
82 1 expect(fn(new Foo())).to.equal(' b c ');
83 1 done();
84 });
85
86 1 it('should expose private variables:', function(done) {
87 var fn = hbs.compile('{{#forOwn this abc=object}} {{@abc.c}} {{/forOwn}}');
88 1 expect(fn(context)).to.equal(' d ');
89 1 done();
90 });
91 });
92
93 1 describe('toPath', function() {
94 it('should return a path from provided arguments', function(done) {
95 expect(hbs.compile('{{toPath "a" "b" "c"}}')()).to.equal('a.b.c');
96 1 done();
97 });
98 1 it('should return a path from calculated arguments', function (done) {
99 var t = hbs.compile('{{toPath "a" (add 1 1) "b"}}')();
100 1 expect(t).to.equal('a.2.b');
101 1 done();
102 });
103 1 it('should return a `get` compatible path', function(done) {
104 var fn = hbs.compile('{{get (toPath "a" (add 1 1) "j") this}}');
105 1 expect(fn({a: [{b: 'c', d: 'e'},{f: 'g', h: 'i'}, {j: 'k', l: 'm'}]})).to.equal('k');
106 1 done();
107 });
108 });
109
110 1 describe('hasOwn', function() {
111 function Foo() {
112 2 this.a = 'b';
113 2 this.b = 'c';
114 }
115 1 Foo.prototype.c = 'd';
116
117 1 it('should return true if object has own property:', function(done) {
118 var fn = hbs.compile('{{hasOwn this "a"}}');
119 1 expect(fn(new Foo())).to.equal('true');
120 1 done();
121 });
122
123 1 it('should return false if object does not have own property:', function(done) {
124 var fn = hbs.compile('{{hasOwn this "c"}}');
125 1 expect(fn(new Foo())).to.equal('false');
126 1 done();
127 });
128 });
129
130 1 describe('isObject', function() {
131 it('should return true if value is an object:', function(done) {
132 var fn = hbs.compile('{{isObject this}}');
133 1 expect(fn({a: 'b'})).to.equal('true');
134 1 done();
135 });
136
137 1 it('should return false if value is not an object:', function(done) {
138 var fn = hbs.compile('{{isObject this}}');
139 1 expect(fn('foo')).to.equal('false');
140 1 done();
141 });
142 });
143
144 1 describe('merge', function() {
145 it('should deeply merge objects passed on the context:', function(done) {
146 var fn = hbs.compile('{{{stringify (merge a b c)}}}');
147 1 var actual = fn({a: {one: 'two'}, b: {one: 'three'}, c: {two: 'four'}});
148 1 expect(actual).to.equal('{"one":"three","two":"four"}');
149 1 done();
150 });
151 });
152
153 1 describe('parseJSON', function() {
154 it('should parse a JSON string:', function(done) {
155 var fn = hbs.compile('{{#JSONparse jsonString}}{{name}}{{/JSONparse}}');
156 1 expect(fn({jsonString: "{\"name\": \"Fry\"}"})).to.equal('Fry');
157 1 done();
158 });
159 });
160
161 1 describe('stringify', function() {
162 it('should stringify an object:', function(done) {
163 var fn = hbs.compile('{{{stringify data}}}');
164 1 var res = fn({data: {name: "Halle", age: 4, userid: "Nicole"}});
165 1 expect(res).to.equal('{"name":"Halle","age":4,"userid":"Nicole"}');
166 1 done();
167 });
168 });
169 });

spec/helpers/3p/string.js

100%
352
352
0
Line Lint Hits Source
1 1 const Code = require('code');
2 1 const Lab = require('lab');
3 1 const lab = exports.lab = Lab.script();
4 1 const expect = Code.expect;
5 1 const it = lab.it;
6 1 const describe = lab.describe
7
8
9 1 const { buildRenderer } = require('../../spec-helpers');
10 1 const renderer = buildRenderer();
11 1 const hbs = renderer.handlebars;
12 1 const helpers = require('../../../helpers/3p/string');
13
14 1 Object.keys(helpers).forEach(key => {
15 hbs.registerHelper(key, helpers[key]);
16 });
17
18 1 describe('string', function() {
19 describe('camelcase', function() {
20 it('should return an empty string if undefined', function(done) {
21 var fn = hbs.compile('{{camelcase}}');
22 1 expect(fn()).to.equal('');
23 1 done();
24 });
25 1 it('should return the string in camelcase', function(done) {
26 var fn = hbs.compile('{{camelcase "foo bar baz qux"}}');
27 1 expect(fn()).to.equal('fooBarBazQux');
28 1 done();
29 });
30 1 it('should lowercase a single character', function(done) {
31 expect(hbs.compile('{{camelcase "f"}}')()).to.equal('f');
32 1 expect(hbs.compile('{{camelcase "A"}}')()).to.equal('a');
33 1 done();
34 });
35 });
36
37 1 describe('capitalize', function() {
38 it('should return an empty string if undefined', function(done) {
39 var fn = hbs.compile('{{capitalize}}');
40 1 expect(fn()).to.equal('');
41 1 done();
42 });
43 1 it('should capitalize a word.', function(done) {
44 var fn = hbs.compile('{{capitalize "foo"}}');
45 1 expect(fn()).to.equal('Foo');
46 1 done();
47 });
48 });
49
50 1 describe('capitalizeAll', function() {
51 it('should return an empty string if undefined', function(done) {
52 var fn = hbs.compile('{{capitalizeAll}}');
53 1 expect(fn()).to.equal('');
54 1 done();
55 });
56 1 it('should return the string with the every word capitalized.', function(done) {
57 var fn = hbs.compile('{{capitalizeAll "bender should not bE allowed on tV"}}');
58 1 expect(fn()).to.equal('Bender Should Not BE Allowed On TV');
59 1 done();
60 });
61 });
62
63 1 describe('center', function() {
64 it('should return an empty string if undefined', function(done) {
65 var fn = hbs.compile('{{center}}');
66 1 expect(fn()).to.equal('');
67 1 done();
68 });
69 1 it('should return the string centered by using non-breaking spaces.', function(done) {
70 var fn = hbs.compile('{{center "Bender should not be allowed on tv." 2}}');
71 1 expect(fn()).to.equal('&amp;nbsp;&amp;nbsp;Bender should not be allowed on tv.&amp;nbsp;&amp;nbsp;');
72 1 done();
73 });
74 });
75
76 1 describe('chop', function() {
77 it('should return an empty string if undefined', function(done) {
78 var fn = hbs.compile('{{chop}}');
79 1 expect(fn()).to.equal('');
80 1 done();
81 });
82 1 it('should remove non-word characters from start of string', function(done) {
83 var fn = hbs.compile('{{chop "- foo bar baz"}}');
84 1 expect(fn()).to.equal('foo bar baz');
85 1 done();
86 });
87 1 it('should remove non-word characters from end of string', function(done) {
88 var fn = hbs.compile('{{chop "foo bar baz _- "}}');
89 1 expect(fn()).to.equal('foo bar baz');
90 1 done();
91 });
92 });
93
94 1 describe('dashcase', function() {
95 it('should return an empty string if undefined', function(done) {
96 var fn = hbs.compile('{{dashcase}}');
97 1 expect(fn()).to.equal('');
98 1 done();
99 });
100 1 it('should return the string in dashcase', function(done) {
101 var fn = hbs.compile('{{dashcase "foo bar baz qux"}}');
102 1 expect(fn()).to.equal('foo-bar-baz-qux');
103 1 done();
104 });
105 1 it('should lowercase a single character', function(done) {
106 expect(hbs.compile('{{dashcase "f"}}')()).to.equal('f');
107 1 expect(hbs.compile('{{dashcase "A"}}')()).to.equal('a');
108 1 done();
109 });
110 });
111
112 1 describe('dotcase', function() {
113 it('should return an empty string if undefined', function(done) {
114 var fn = hbs.compile('{{dotcase}}');
115 1 expect(fn()).to.equal('');
116 1 done();
117 });
118 1 it('should return the string in dotcase', function(done) {
119 var fn = hbs.compile('{{dotcase "foo bar baz qux"}}');
120 1 expect(fn()).to.equal('foo.bar.baz.qux');
121 1 done();
122 });
123 1 it('should lowercase a single character', function(done) {
124 expect(hbs.compile('{{dotcase "f"}}')()).to.equal('f');
125 1 expect(hbs.compile('{{dotcase "A"}}')()).to.equal('a');
126 1 done();
127 });
128 });
129
130 1 describe('hyphenate', function() {
131 it('should return an empty string if undefined', function(done) {
132 var fn = hbs.compile('{{hyphenate}}');
133 1 expect(fn()).to.equal('');
134 1 done();
135 });
136 1 it('should return the string with spaces replaced with hyphens.', function(done) {
137 var fn = hbs.compile('{{hyphenate "Bender should not be allowed on tv."}}');
138 1 expect(fn()).to.equal('Bender-should-not-be-allowed-on-tv.');
139 1 done();
140 });
141 });
142
143 1 describe('lowercase', function() {
144 it('should return an empty string if undefined', function(done) {
145 var fn = hbs.compile('{{lowercase}}');
146 1 expect(fn()).to.equal('');
147 1 done();
148 });
149 1 it('should return the string in lowercase', function(done) {
150 var fn = hbs.compile('{{lowercase "BENDER SHOULD NOT BE ALLOWED ON TV"}}');
151 1 expect(fn()).to.equal('bender should not be allowed on tv');
152 1 done();
153 });
154 });
155
156 1 describe('pascalcase', function() {
157 it('should return an empty string if undefined', function(done) {
158 var fn = hbs.compile('{{pascalcase}}');
159 1 expect(fn()).to.equal('');
160 1 done();
161 });
162 1 it('should return the string in pascalcase', function(done) {
163 var fn = hbs.compile('{{pascalcase "foo bar baz qux"}}');
164 1 expect(fn()).to.equal('FooBarBazQux');
165 1 done();
166 });
167 1 it('should uppercase a single character', function(done) {
168 expect(hbs.compile('{{pascalcase "f"}}')()).to.equal('F');
169 1 expect(hbs.compile('{{pascalcase "A"}}')()).to.equal('A');
170 1 done();
171 });
172 });
173
174 1 describe('pathcase', function() {
175 it('should return an empty string if undefined', function(done) {
176 var fn = hbs.compile('{{pathcase}}');
177 1 expect(fn()).to.equal('');
178 1 done();
179 });
180 1 it('should return the string in pathcase', function(done) {
181 var fn = hbs.compile('{{pathcase "foo bar baz qux"}}');
182 1 expect(fn()).to.equal('foo/bar/baz/qux');
183 1 done();
184 });
185 1 it('should lowercase a single character', function(done) {
186 expect(hbs.compile('{{pathcase "f"}}')()).to.equal('f');
187 1 expect(hbs.compile('{{pathcase "A"}}')()).to.equal('a');
188 1 done();
189 });
190 });
191
192 1 describe('plusify', function() {
193 it('should return an empty string if undefined', function(done) {
194 var fn = hbs.compile('{{plusify}}');
195 1 expect(fn()).to.equal('');
196 1 done();
197 });
198 1 it('should return the empty string with no change.', function(done) {
199 var fn = hbs.compile('{{plusify ""}}');
200 1 expect(fn()).to.equal('');
201 1 done();
202 });
203 1 it('should return the string with no change.', function(done) {
204 var fn = hbs.compile('{{plusify "BenderShouldNotBeAllowedOnTv."}}');
205 1 expect(fn()).to.equal('BenderShouldNotBeAllowedOnTv.');
206 1 done();
207 });
208 1 it('should return the string with spaces replaced with pluses.', function(done) {
209 var fn = hbs.compile('{{plusify "Bender should not be allowed on tv."}}');
210 1 expect(fn()).to.equal('Bender+should+not+be+allowed+on+tv.');
211 1 done();
212 });
213 });
214
215 1 describe('replace', function() {
216 it('should return an empty string if undefined', function(done) {
217 var fn = hbs.compile('{{replace}}');
218 1 expect(fn()).to.equal('');
219 1 done();
220 });
221 1 it('should replace occurrences of string "A" with string "B"', function(done) {
222 var fn = hbs.compile('{{replace "Bender Bending Rodriguez" "B" "M"}}');
223 1 expect(fn()).to.equal('Mender Mending Rodriguez');
224 1 done();
225 });
226 1 it('should return the string if `a` is undefined', function(done) {
227 var fn = hbs.compile('{{replace "a b c"}}');
228 1 expect(fn()).to.equal('a b c');
229 1 done();
230 });
231 1 it('should replace the string with `""` if `b` is undefined', function(done) {
232 var fn = hbs.compile('{{replace "a b c" "a"}}');
233 1 expect(fn()).to.equal(' b c');
234 1 done();
235 });
236 });
237
238 1 describe('reverse', function() {
239 it('should return an empty string if undefined', function(done) {
240 var fn = hbs.compile('{{reverse}}');
241 1 expect(fn()).to.equal('');
242 1 done();
243 });
244 1 it('should return the string in reverse.', function(done) {
245 var fn = hbs.compile('{{reverse "bender should NOT be allowed on TV."}}');
246 1 expect(fn()).to.equal('.VT no dewolla eb TON dluohs redneb');
247 1 done();
248 });
249 });
250
251 1 describe('sentence', function() {
252 it('should return an empty string if undefined', function(done) {
253 var fn = hbs.compile('{{sentence}}');
254 1 expect(fn()).to.equal('');
255 1 done();
256 });
257 1 it('should capitalize the first word of each sentence in a string and convert the rest of the sentence to lowercase.', function(done) {
258 var fn = hbs.compile('{{sentence "bender should NOT be allowed on TV. fry SHOULD be allowed on TV."}}');
259 1 expect(fn()).to.equal('Bender should not be allowed on tv. Fry should be allowed on tv.');
260 1 done();
261 });
262 });
263
264 1 describe('snakecase', function() {
265 it('should return an empty string if undefined', function(done) {
266 var fn = hbs.compile('{{snakecase}}');
267 1 expect(fn()).to.equal('');
268 1 done();
269 });
270 1 it('should lowercase a single character', function(done) {
271 expect(hbs.compile('{{snakecase "a"}}')()).to.equal('a');
272 1 expect(hbs.compile('{{snakecase "A"}}')()).to.equal('a');
273 1 done();
274 });
275 1 it('should return the string in snakecase', function(done) {
276 var fn = hbs.compile('{{snakecase "foo bar baz qux"}}');
277 1 expect(fn()).to.equal('foo_bar_baz_qux');
278 1 done();
279 });
280 });
281
282 1 describe('split', function() {
283 it('should return an empty string if undefined', function(done) {
284 var fn = hbs.compile('{{split}}');
285 1 expect(fn()).to.equal('');
286 1 done();
287 });
288 1 it('should split the string with the default character', function(done) {
289 var fn = hbs.compile('{{#each (split "a,b,c")}}<{{.}}>{{/each}}');
290 1 expect(fn()).to.equal('<a><b><c>');
291 1 done();
292 });
293 1 it('should split the string on the given character', function(done) {
294 var fn = hbs.compile('{{#each (split "a|b|c" "|")}}<{{.}}>{{/each}}');
295 1 expect(fn()).to.equal('<a><b><c>');
296 1 done();
297 });
298 });
299
300 1 describe('titleize', function() {
301 it('should return an empty string if undefined', function(done) {
302 var fn = hbs.compile('{{titleize}}');
303 1 expect(fn()).to.equal('');
304 1 done();
305 });
306
307 1 it('should return unchanged input string if string has only non-word characters', function(done) {
308 var fn = hbs.compile('{{titleize ",!"}}');
309 1 expect(fn()).to.equal(',!');
310 1 done();
311 });
312
313 1 it('should return the string in title case.', function(done) {
314 var fn = hbs.compile('{{titleize "Bender-should-Not-be-allowed_on_Tv"}}');
315 1 expect(fn()).to.equal('Bender Should Not Be Allowed On Tv');
316 1 done();
317 });
318 });
319
320 1 describe('trim', function() {
321 it('should return an empty string if undefined', function(done) {
322 var fn = hbs.compile('{{trim}}');
323 1 expect(fn()).to.equal('');
324 1 done();
325 });
326 1 it('should trim leading whitespace', function(done) {
327 var fn = hbs.compile('{{trim " foo"}}');
328 1 expect(fn()).to.equal('foo');
329 1 done();
330 });
331 1 it('should trim trailing whitespace', function(done) {
332 var fn = hbs.compile('{{trim "foo "}}');
333 1 expect(fn()).to.equal('foo');
334 1 done();
335 });
336 });
337
338 1 describe('startsWith', function() {
339 it('should return an empty string if undefined', function(done) {
340 var fn = hbs.compile('{{startsWith}}');
341 1 expect(fn()).to.equal('');
342 1 done();
343 });
344 1 it('should render "Yes he is", from inside the block.', function(done) {
345 var fn = hbs.compile('{{#startsWith "Bender" "Bender is great"}}Yes he is{{/startsWith}}');
346 1 expect(fn()).to.equal("Yes he is");
347 1 done();
348 });
349 1 it('should render the Inverse block.', function(done) {
350 var fn = hbs.compile('{{#startsWith "Goodbye" "Hello, world!"}}Whoops{{else}}Bro, do you even hello world?{{/startsWith}}');
351 1 expect(fn()).to.equal('Bro, do you even hello world?');
352 1 done();
353 });
354 1 it("should render the Inverse block.", function(done) {
355 var fn = hbs.compile('{{#startsWith "myPrefix" nullProperty}}fn block{{else}}inverse block{{/startsWith}}');
356 1 expect(fn()).to.equal('inverse block');
357 1 done();
358 });
359 });
360
361 1 describe('uppercase', function() {
362 it('should return an empty string if undefined', function(done) {
363 var fn = hbs.compile('{{uppercase}}');
364 1 expect(fn()).to.equal('');
365 1 done();
366 });
367
368 1 it('should return the string in uppercase', function(done) {
369 var fn = hbs.compile('{{uppercase "bender should not be allowed on tv"}}');
370 1 expect(fn()).to.equal('BENDER SHOULD NOT BE ALLOWED ON TV');
371 1 done();
372 });
373
374 1 it('should work as a block helper', function(done) {
375 var fn = hbs.compile('{{#uppercase}}bender should not be allowed on tv{{/uppercase}}');
376 1 expect(fn()).to.equal('BENDER SHOULD NOT BE ALLOWED ON TV');
377 1 done();
378 });
379 });
380 });
381

spec/helpers/3p/url.js

100%
96
96
0
Line Lint Hits Source
1 1 const Code = require('code');
2 1 const Lab = require('lab');
3 1 const lab = exports.lab = Lab.script();
4 1 const expect = Code.expect;
5 1 const it = lab.it;
6 1 const describe = lab.describe
7
8 1 const { buildRenderer } = require('../../spec-helpers');
9 1 const renderer = buildRenderer();
10 1 const hbs = renderer.handlebars;
11 1 const helpers = require('../../../helpers/3p/misc');
12
13 1 Object.keys(helpers).forEach(key => {
14 hbs.registerHelper(key, helpers[key]);
15 });
16
17
18 1 describe('url', function() {
19 describe('urlResolve', function() {
20 it('should take a base URL, and a href URL, and resolve them as a browser would', function(done) {
21 var fn = hbs.compile('{{urlResolve "/one/two/three" "four"}}');
22 1 expect(fn()).to.equal('/one/two/four');
23 1 done();
24 });
25
26 1 it('should take a base URL, and a href URL, and resolve them as a browser would', function(done) {
27 var fn = hbs.compile('{{urlResolve "http://example.com/" "/one"}}');
28 1 expect(fn()).to.equal('http://example.com/one');
29 1 done();
30 });
31
32 1 it('should take a base URL, and a href URL, and resolve them as a browser would', function(done) {
33 var fn = hbs.compile('{{urlResolve "http://example.com/one" "/two"}}');
34 1 expect(fn()).to.equal('http://example.com/two');
35 1 done();
36 });
37 });
38
39 1 describe('encodeURI', function() {
40 it('should return an encoded uri string.', function(done) {
41 var fn = hbs.compile('{{encodeURI "http://example.com?comment=Thyme &time=again"}}');
42 1 expect(fn()).to.equal('http%3A%2F%2Fexample.com%3Fcomment%3DThyme%20%26time%3Dagain');
43 1 done();
44 });
45 });
46
47 1 describe('decodeURI', function() {
48 it('should return an decoded uri string.', function(done) {
49 var fn = hbs.compile('{{{decodeURI "http%3A%2F%2Fexample.com%3Fcomment%3DThyme%20%26time%3Dagain"}}}');
50 1 expect(fn()).to.equal('http://example.com?comment=Thyme &time=again');
51 1 done();
52 });
53 });
54
55 1 describe('urlParse', function() {
56 it('should take a string, and return an object stringified to JSON.', function(done) {
57 var fn = hbs.compile('{{{JSONstringify (urlParse "http://foo.com/bar/baz?key=value" "json")}}}');
58 1 expect(JSON.parse(fn())).to.equal({
59 "protocol": "http:",
60 "slashes": true,
61 "auth": null,
62 "host": "foo.com",
63 "port": null,
64 "hostname": "foo.com",
65 "hash": null,
66 "search": "?key=value",
67 "query": "key=value",
68 "pathname": "/bar/baz",
69 "path": "/bar/baz?key=value",
70 "href": "http://foo.com/bar/baz?key=value"
71 });
72 1 done();
73 });
74 });
75
76 1 describe('strip protocol', function() {
77 it('should take an http url and return without the protocol', function(done) {
78 var data = { testUrl: 'http://foo.bar' };
79 1 var expectedResult = '//foo.bar/';
80 1 var fn = hbs.compile('{{stripProtocol testUrl}}');
81 1 expect(fn(data)).to.equal(expectedResult);
82 1 done();
83 });
84
85 1 it('strip https protocol', function(done) {
86 var data = { testUrl: 'https://foo.bar' };
87 1 var expectedResult = '//foo.bar/';
88 1 var fn = hbs.compile('{{stripProtocol testUrl}}');
89 1 expect(fn(data)).to.equal(expectedResult);
90 1 done();
91 });
92
93 1 it('should leave a relative url unchanged', function(done) {
94 var testUrl = 'path/to/file';
95 1 var data = { testUrl: testUrl };
96 1 var fn = hbs.compile('{{stripProtocol testUrl}}');
97 1 expect(fn(data)).to.equal(testUrl);
98 1 done();
99 });
100
101 1 it('should leave an absolute url unchanged', function(done) {
102 var testUrl = '/path/to/file';
103 1 var data = { testUrl: testUrl };
104 1 var fn = hbs.compile('{{stripProtocol testUrl}}');
105 1 expect(fn(data)).to.equal(testUrl);
106 1 done();
107 });
108
109 });
110 });

spec/helpers/3p/utils/lib/arrayFilter.js

100%
40
40
0
Line Lint Hits Source
1 /*
2 * COPY of https://raw.githubusercontent.com/jonschlinkert/arr-filter/1.1.1/test.js
3 */
4
5 1 const Code = require('code');
6 1 const Lab = require('lab');
7 1 const lab = exports.lab = Lab.script();
8 1 const expect = Code.expect;
9 1 const it = lab.it;
10 1 const describe = lab.describe;
11 1 const path = require('path');
12
13 1 const filter = require('../../../../../helpers/3p/utils/lib/arrayFilter');
14
15 1 describe('filter:', function () {
16 it('should filter strings from the array:', function (done) {
17 var actual = filter(['a', {a: 'b'}, 1, 'b', 2, {c: 'd'}, 'c'], function (ele) {
18 return typeof ele === 'string';
19 });
20 1 expect(actual).to.equal(['a', 'b', 'c']);
21 1 done();
22 });
23
24 1 it('should filter objects:', function (done) {
25 var actual = filter(['a', {a: 'b'}, 1, 'b', 2, {c: 'd'}, 'c'], function (ele) {
26 return typeof ele === 'object';
27 });
28 1 expect(actual).to.equal([{a: 'b'}, {c: 'd'}]);
29 1 done();
30 });
31
32 1 it('should filter strings:', function (done) {
33 function ext(extension) {
34 2 return function(fp) {
35 return path.extname(fp) === extension;
36 };
37 }
38
39 1 expect(filter(['a/b/c.js', 'a/b/c.css'], ext('.css'))).to.equal(['a/b/c.css']);
40 1 expect(filter(['a/b/c.js', 'a/b/c.css'], ext('.js'))).to.equal(['a/b/c.js']);
41 1 done();
42 });
43
44 1 it('should throw an error when the callback is missing.', function (done) {
45 expect(function() {
46 filter(['a/b/c.js', 'a/b/c.css']);
47 }).to.throw('arr-filter expects a callback function.');
48 1 done();
49 });
50 });

spec/helpers/3p/utils/lib/arrayFlatten.js

100%
19
19
0
Line Lint Hits Source
1 1 const Code = require('code');
2 1 const Lab = require('lab');
3 1 const lab = exports.lab = Lab.script();
4 1 const expect = Code.expect;
5 1 const it = lab.it;
6 1 const describe = lab.describe;
7
8 1 const flatten = require('../../../../../helpers/3p/utils/lib/arrayFlatten');
9
10 1 describe('flatten', function() {
11 it('should flatten nested arrays:', function(done) {
12 expect(flatten(['a', 'b', ['c'], 'd', ['e']])).to.equal(['a', 'b', 'c', 'd', 'e']);
13 1 done();
14 });
15
16 1 it('should flatten deeply nested arrays:', function(done) {
17 expect(flatten(['a', [[[[[[[['b', [['c']]]]]], 'd', ['e']]]]]])).to.equal(['a', 'b', 'c', 'd', 'e']);
18 1 expect(flatten(['a', 'b', ['c'], 'd', ['e']])).to.equal(['a', 'b', 'c', 'd', 'e']);
19 1 expect(flatten([['a', ['b', ['k', ['a', ['b', ['c'], [['a', [['a', ['b', ['k', ['a', ['b', ['c']], ['a', ['x', ['c'], ['a', ['x', ['k']]], ['d', ['z']]]], ['d', ['m']]], ['d', ['e']]]]], ['d', ['e']]], ['b', ['k', ['a', ['b', ['c']], ['a', ['x', ['c'], ['a', ['x', ['k']]], ['d', ['z']]]], ['d', ['m']]], ['d', ['e']]]]], ['d', ['e']]]], ['a', ['x', ['c'], ['a', ['x', ['k']], [['a', ['b', ['k', ['a', ['b', ['c']], ['a', ['x', ['c'], ['a', ['x', ['k']]], ['d', ['z']]]], ['d', ['m']]], ['d', ['e']]]]], ['d', ['e']]]], ['d', ['z']]]], ['d', ['m']]], ['d', ['e']]]]], ['d', ['e']]])).to.equal([ 'a', 'b', 'k', 'a', 'b', 'c', 'a', 'a', 'b', 'k', 'a', 'b', 'c', 'a', 'x', 'c', 'a', 'x', 'k', 'd', 'z', 'd', 'm', 'd', 'e', 'd', 'e', 'b', 'k', 'a', 'b', 'c', 'a', 'x', 'c', 'a', 'x', 'k', 'd', 'z', 'd', 'm', 'd', 'e', 'd', 'e', 'a', 'x', 'c', 'a', 'x', 'k', 'a', 'b', 'k', 'a', 'b', 'c', 'a', 'x', 'c', 'a', 'x', 'k', 'd', 'z', 'd', 'm', 'd', 'e', 'd', 'e', 'd', 'z', 'd', 'm', 'd', 'e', 'd', 'e' ]);
20 1 done();
21 });
22 });
23

spec/helpers/3p/utils/lib/arraySort.js

100%
252
252
0
Line Lint Hits Source
1 1 const Code = require('code');
2 1 const Lab = require('lab');
3 1 const lab = exports.lab = Lab.script();
4 1 const expect = Code.expect;
5 1 const it = lab.it;
6 1 const describe = lab.describe;
7
8 1 const arraySort = require('../../../../../helpers/3p/utils/lib/arraySort');
9 1 const {getValue: get } = require('../../../../../helpers/lib/common');
10
11 1 describe('errors', function() {
12 it('should throw an error when invalid args are passed:', function(done) {
13 expect(function() {
14 arraySort({});
15 }).to.throw('array-sort expects an array.');
16 1 done();
17 });
18 });
19
20 1 describe('empty array', function() {
21 it('should return an empty array when null or undefined is passed', function(done) {
22 expect(arraySort()).to.equal([]);
23 1 expect(arraySort(undefined)).to.equal([]);
24 1 expect(arraySort(null)).to.equal([]);
25 1 done();
26 })
27 });
28
29 1 describe('basic sort', function() {
30 it('should sort an array of primitives', function(done) {
31 var arr = ['d', 3, 'b', 'a', 'd', 1, 0, 'z'];
32 1 expect(arraySort(arr)).to.equal([ 0, 1, 3, 'a', 'b', 'd', 'd', 'z' ]);
33 1 done();
34 })
35 });
36
37 1 describe('arraySort', function() {
38 var posts = [
39 { path: 'a.md', locals: { date: '2014-01-09' } },
40 { path: 'f.md', locals: { date: '2014-01-02' } },
41 { path: 'd.md', locals: { date: '2013-05-06' } },
42 { path: 'e.md', locals: { date: '2015-01-02' } },
43 { path: 'b.md', locals: { date: '2012-01-02' } },
44 { path: 'f.md', locals: { date: '2014-06-01' } },
45 { path: 'c.md', locals: { date: '2015-04-12' } },
46 { path: 'g.md', locals: { date: '2014-02-02' } },
47 ];
48
49 1 it('should sort by a property:', function(done) {
50 var arr = [{key: 'y'}, {key: 'z'}, {key: 'x'}];
51 1 expect(arraySort(arr, 'key')).to.equal([
52 {key: 'x'},
53 {key: 'y'},
54 {key: 'z'}
55 ]);
56
57 1 expect(arraySort(posts, 'path')).to.equal([
58 { path: 'a.md', locals: { date: '2014-01-09' } },
59 { path: 'b.md', locals: { date: '2012-01-02' } },
60 { path: 'c.md', locals: { date: '2015-04-12' } },
61 { path: 'd.md', locals: { date: '2013-05-06' } },
62 { path: 'e.md', locals: { date: '2015-01-02' } },
63 { path: 'f.md', locals: { date: '2014-01-02' } },
64 { path: 'f.md', locals: { date: '2014-06-01' } },
65 { path: 'g.md', locals: { date: '2014-02-02' } }
66 ]);
67 1 done();
68 });
69
70 1 it('should sort by a nested property:', function(done) {
71 var res = expect(arraySort(posts, 'locals.date'));
72 1 res.to.equal([
73 { path: 'b.md', locals: { date: '2012-01-02' } },
74 { path: 'd.md', locals: { date: '2013-05-06' } },
75 { path: 'f.md', locals: { date: '2014-01-02' } },
76 { path: 'a.md', locals: { date: '2014-01-09' } },
77 { path: 'g.md', locals: { date: '2014-02-02' } },
78 { path: 'f.md', locals: { date: '2014-06-01' } },
79 { path: 'e.md', locals: { date: '2015-01-02' } },
80 { path: 'c.md', locals: { date: '2015-04-12' } }
81 ]);
82 1 done();
83 });
84
85 1 it('should do nothing when the specified property is not a string:', function(done) {
86 var arr = [
87 {a: {b: {c: 'c'}}},
88 {a: {b: {z: 'z'}}},
89 {a: {b: {u: 'u'}}},
90 {a: {b: {y: 'y'}}}
91 ];
92
93 1 expect(arraySort(arr, 'a.b')).to.equal([
94 {a: {b: {c: 'c'}}},
95 {a: {b: {z: 'z'}}},
96 {a: {b: {u: 'u'}}},
97 {a: {b: {y: 'y'}}}
98 ]);
99 1 done();
100 });
101
102 1 it('should sort by multiple properties:', function(done) {
103 var posts = [
104 { foo: 'bbb', locals: { date: '2013-05-06' } },
105 { foo: 'aaa', locals: { date: '2012-01-02' } },
106 { foo: 'ddd', locals: { date: '2015-04-12' } },
107 { foo: 'ccc', locals: { date: '2014-01-02' } },
108 { foo: 'ccc', locals: { date: '2015-01-02' } },
109 { foo: 'ddd', locals: { date: '2014-01-09' } },
110 { foo: 'bbb', locals: { date: '2014-06-01' } },
111 { foo: 'aaa', locals: { date: '2014-02-02' } },
112 ];
113
114 1 var actual = arraySort(posts, ['foo', 'locals.date']);
115
116 1 expect(actual).to.equal([
117 { foo: 'aaa', locals: { date: '2012-01-02' } },
118 { foo: 'aaa', locals: { date: '2014-02-02' } },
119 { foo: 'bbb', locals: { date: '2013-05-06' } },
120 { foo: 'bbb', locals: { date: '2014-06-01' } },
121 { foo: 'ccc', locals: { date: '2014-01-02' } },
122 { foo: 'ccc', locals: { date: '2015-01-02' } },
123 { foo: 'ddd', locals: { date: '2014-01-09' } },
124 { foo: 'ddd', locals: { date: '2015-04-12' } }
125 ]);
126 1 done();
127 });
128
129 // TODO: Not working!
130
131 // it('should sort with a function:', function(done) {
132 // var arr = [{key: 'y'}, {key: 'z'}, {key: 'x'}];
133
134 // var actual = arraySort(arr, function(a, b) {
135 // return a.key > b.key;
136 // });
137
138 // expect(actual).to.equal([
139 // {key: 'x'},
140 // {key: 'y'},
141 // {key: 'z'}
142 // ]);
143 // done();
144 // });
145
146 1 it('should support sorting with a list of function:', function(done) {
147 var arr = [
148 {foo: 'w', bar: 'y', baz: 'w', quux: 'a'},
149 {foo: 'x', bar: 'y', baz: 'w', quux: 'b'},
150 {foo: 'x', bar: 'y', baz: 'z', quux: 'c'},
151 {foo: 'x', bar: 'x', baz: 'w', quux: 'd'},
152 ];
153
154 1 var compare = function(prop) {
155 return function(a, b) {
156 return a[prop].localeCompare(b[prop]);
157 };
158 };
159
160 1 var actual = arraySort(arr,
161 compare('foo'),
162 compare('bar'),
163 compare('baz'),
164 compare('quux'));
165
166 1 expect(actual).to.equal([
167 { foo: 'w', bar: 'y', baz: 'w', quux: 'a' },
168 { foo: 'x', bar: 'x', baz: 'w', quux: 'd' },
169 { foo: 'x', bar: 'y', baz: 'w', quux: 'b' },
170 { foo: 'x', bar: 'y', baz: 'z', quux: 'c' }
171 ]);
172 1 done();
173 });
174
175 1 it('should support sorting with an array of function:', function(done) {
176 var arr = [
177 {foo: 'w', bar: 'y', baz: 'w', quux: 'a'},
178 {foo: 'x', bar: 'y', baz: 'w', quux: 'b'},
179 {foo: 'x', bar: 'y', baz: 'z', quux: 'c'},
180 {foo: 'x', bar: 'x', baz: 'w', quux: 'd'},
181 ];
182
183 1 var compare = function(prop) {
184 return function(a, b) {
185 return a[prop].localeCompare(b[prop]);
186 };
187 };
188
189 1 var actual = arraySort(arr, [
190 compare('foo'),
191 compare('bar'),
192 compare('baz'),
193 compare('quux')
194 ]);
195
196 1 expect(actual).to.equal([
197 { foo: 'w', bar: 'y', baz: 'w', quux: 'a' },
198 { foo: 'x', bar: 'x', baz: 'w', quux: 'd' },
199 { foo: 'x', bar: 'y', baz: 'w', quux: 'b' },
200 { foo: 'x', bar: 'y', baz: 'z', quux: 'c' }
201 ]);
202 1 done();
203 });
204
205 1 it('should support sorting with any combination of functions and properties:', function(done) {
206 var posts = [
207 { path: 'a.md', locals: { date: '2014-01-01', foo: 'zzz', bar: 1 } },
208 { path: 'f.md', locals: { date: '2014-01-01', foo: 'mmm', bar: 2 } },
209 { path: 'd.md', locals: { date: '2014-01-01', foo: 'xxx', bar: 3 } },
210 { path: 'i.md', locals: { date: '2014-01-01', foo: 'xxx', bar: 5 } },
211 { path: 'k.md', locals: { date: '2014-01-01', foo: 'xxx', bar: 1 } },
212 { path: 'j.md', locals: { date: '2014-01-01', foo: 'xxx', bar: 4 } },
213 { path: 'h.md', locals: { date: '2014-01-01', foo: 'xxx', bar: 6 } },
214 { path: 'l.md', locals: { date: '2014-01-01', foo: 'xxx', bar: 7 } },
215 { path: 'e.md', locals: { date: '2015-01-02', foo: 'aaa', bar: 8 } },
216 { path: 'b.md', locals: { date: '2012-01-02', foo: 'ccc', bar: 9 } },
217 { path: 'f.md', locals: { date: '2014-06-01', foo: 'rrr', bar: 10 } },
218 { path: 'c.md', locals: { date: '2015-04-12', foo: 'ttt', bar: 11 } },
219 { path: 'g.md', locals: { date: '2014-02-02', foo: 'yyy', bar: 12 } },
220 ];
221
222 1 var compare = function(prop) {
223 return function(a, b, fn) {
224 var valA = get(a, prop);
225 27 var valB = get(b, prop);
226 27 return fn(valA, valB);
227 };
228 };
229
230 1 var actual = arraySort(posts, 'locals.date', 'doesnt.exist', compare('locals.foo'), [
231 compare('locals.bar')
232 ]);
233
234 1 expect(actual).to.equal([
235 { path: 'b.md', locals: { date: '2012-01-02', foo: 'ccc', bar: 9 } },
236 { path: 'f.md', locals: { date: '2014-01-01', foo: 'mmm', bar: 2 } },
237 { path: 'k.md', locals: { date: '2014-01-01', foo: 'xxx', bar: 1 } },
238 { path: 'd.md', locals: { date: '2014-01-01', foo: 'xxx', bar: 3 } },
239 { path: 'j.md', locals: { date: '2014-01-01', foo: 'xxx', bar: 4 } },
240 { path: 'i.md', locals: { date: '2014-01-01', foo: 'xxx', bar: 5 } },
241 { path: 'h.md', locals: { date: '2014-01-01', foo: 'xxx', bar: 6 } },
242 { path: 'l.md', locals: { date: '2014-01-01', foo: 'xxx', bar: 7 } },
243 { path: 'a.md', locals: { date: '2014-01-01', foo: 'zzz', bar: 1 } },
244 { path: 'g.md', locals: { date: '2014-02-02', foo: 'yyy', bar: 12 } },
245 { path: 'f.md', locals: { date: '2014-06-01', foo: 'rrr', bar: 10 } },
246 { path: 'e.md', locals: { date: '2015-01-02', foo: 'aaa', bar: 8 } },
247 { path: 'c.md', locals: { date: '2015-04-12', foo: 'ttt', bar: 11 } }
248 ]);
249 1 done();
250 });
251
252 1 it('should support reverse sorting with any combination of functions and properties:', function(done) {
253 var posts = [
254 { path: 'a.md', locals: { date: '2014-01-01', foo: 'zzz', bar: 1 } },
255 { path: 'f.md', locals: { date: '2014-01-01', foo: 'mmm', bar: 2 } },
256 { path: 'd.md', locals: { date: '2014-01-01', foo: 'xxx', bar: 3 } },
257 { path: 'i.md', locals: { date: '2014-01-01', foo: 'xxx', bar: 5 } },
258 { path: 'k.md', locals: { date: '2014-01-01', foo: 'xxx', bar: 1 } },
259 { path: 'j.md', locals: { date: '2014-01-01', foo: 'xxx', bar: 4 } },
260 { path: 'h.md', locals: { date: '2014-01-01', foo: 'xxx', bar: 6 } },
261 { path: 'l.md', locals: { date: '2014-01-01', foo: 'xxx', bar: 7 } },
262 { path: 'e.md', locals: { date: '2015-01-02', foo: 'aaa', bar: 8 } },
263 { path: 'b.md', locals: { date: '2012-01-02', foo: 'ccc', bar: 9 } },
264 { path: 'f.md', locals: { date: '2014-06-01', foo: 'rrr', bar: 10 } },
265 { path: 'c.md', locals: { date: '2015-04-12', foo: 'ttt', bar: 11 } },
266 { path: 'g.md', locals: { date: '2014-02-02', foo: 'yyy', bar: 12 } },
267 ];
268
269 1 var compare = function(prop) {
270 return function(a, b, fn) {
271 var valA = get(a, prop);
272 24 var valB = get(b, prop);
273 24 return fn(valA, valB);
274 };
275 };
276
277 1 var actual = arraySort(posts, 'locals.date', 'doesnt.exist', compare('locals.foo'), [
278 compare('locals.bar')
279 ], { reverse: true });
280
281 1 expect(actual).to.equal([
282 { path: 'c.md', locals: { date: '2015-04-12', foo: 'ttt', bar: 11 } },
283 { path: 'e.md', locals: { date: '2015-01-02', foo: 'aaa', bar: 8 } },
284 { path: 'f.md', locals: { date: '2014-06-01', foo: 'rrr', bar: 10 } },
285 { path: 'g.md', locals: { date: '2014-02-02', foo: 'yyy', bar: 12 } },
286 { path: 'a.md', locals: { date: '2014-01-01', foo: 'zzz', bar: 1 } },
287 { path: 'l.md', locals: { date: '2014-01-01', foo: 'xxx', bar: 7 } },
288 { path: 'h.md', locals: { date: '2014-01-01', foo: 'xxx', bar: 6 } },
289 { path: 'i.md', locals: { date: '2014-01-01', foo: 'xxx', bar: 5 } },
290 { path: 'j.md', locals: { date: '2014-01-01', foo: 'xxx', bar: 4 } },
291 { path: 'd.md', locals: { date: '2014-01-01', foo: 'xxx', bar: 3 } },
292 { path: 'k.md', locals: { date: '2014-01-01', foo: 'xxx', bar: 1 } },
293 { path: 'f.md', locals: { date: '2014-01-01', foo: 'mmm', bar: 2 } },
294 { path: 'b.md', locals: { date: '2012-01-02', foo: 'ccc', bar: 9 } }
295 ]);
296 1 done();
297 });
298 });

spec/helpers/3p/utils/lib/createFrame.js

100%
74
74
0
Line Lint Hits Source
1 1 const Code = require('code');
2 1 const Lab = require('lab');
3 1 const lab = exports.lab = Lab.script();
4 1 const expect = Code.expect;
5 1 const it = lab.it;
6 1 const describe = lab.describe;
7
8 1 const createFrame = require('../../../../../helpers/3p/utils/lib/createFrame');
9
10 1 var hbs = require('@bigcommerce/handlebars-v4');
11
12 1 describe('createFrame', function () {
13 it('should create a reference to _parent:', function (done) {
14 var obj = createFrame({});
15 1 expect(obj.hasOwnProperty('_parent')).to.equal(true);
16 1 done();
17 });
18
19 1 it('should expose a non-enumerable `extend` method:', function (done) {
20 var obj = createFrame({});
21 1 expect(typeof obj.extend).to.equal('function');
22 1 done();
23 });
24
25 1 it('should extend the frame object with the `extend` method:', function (done) {
26 var obj = createFrame({});
27 1 obj.extend({a: 'aaa'});
28 1 obj.extend({b: 'bbb'});
29 1 expect(obj.hasOwnProperty('a')).to.equal(true);
30 1 expect(obj.hasOwnProperty('b')).to.equal(true);
31 1 done();
32 });
33
34 1 it('should extend the frame object with additional objects:', function (done) {
35 var obj = createFrame({}, {a: 'aaa'}, {b: 'bbb'});
36 1 expect(obj.hasOwnProperty('a')).to.equal(true);
37 1 expect(obj.hasOwnProperty('b')).to.equal(true);
38 1 done();
39 });
40
41 1 it('should work with sparse arguments:', function (done) {
42 var obj = createFrame({}, undefined, {b: 'bbb'});
43 1 expect(obj.hasOwnProperty('b')).to.equal(true);
44 1 done();
45 });
46
47 1 it('should add private variables when passed to `options.fn()`:', function (done) {
48 var tmpl = '{{#foo}}{{@a}}{{@b}}{{/foo}}';
49
50 1 hbs.registerHelper('foo', function (context) {
51 var frame = createFrame(context.data);
52 1 frame.extend({a: 'aaa'});
53 1 frame.extend({b: 'bbb'});
54 1 return context.fn(context, {data: frame});
55 });
56 1 var fn = hbs.compile(tmpl);
57 1 expect(fn({})).to.equal('aaabbb');
58 1 done();
59 });
60
61 1 it('should create private variables from options hash properties', function (done) {
62 var tmpl = '{{#foo options target=xxx}}{{@a}}{{@b}}{{log}}{{@target.yyy}}{{/foo}}';
63 1 var context = {
64 options: {data: {eee: 'fff'} },
65 xxx: {yyy: 'zzz'}
66 };
67 1 hbs.registerHelper('foo', function (context, options) {
68 var frame = createFrame(context.data);
69 1 frame.extend({a: 'aaa'});
70 1 frame.extend({b: 'bbb'});
71 1 frame.extend(options.hash);
72 1 return options.fn(context, {data: frame});
73 });
74
75 1 var fn = hbs.compile(tmpl);
76 1 expect(fn(context)).to.equal('aaabbbzzz');
77 1 done();
78 });
79
80 1 it('should throw an error if args are invalid:', function (done) {
81 expect(function () {
82 createFrame();
83 }, /createFrame expects data to be an object/).to.throw();
84 1 done();
85 });
86 });

spec/helpers/3p/utils/lib/forIn.js

100%
37
37
0
Line Lint Hits Source
1 1 const Code = require('code');
2 1 const Lab = require('lab');
3 1 const lab = exports.lab = Lab.script();
4 1 const expect = Code.expect;
5 1 const it = lab.it;
6 1 const describe = lab.describe;
7
8 1 const forIn = require('../../../../../helpers/3p/utils/lib/forIn');
9
10
11 1 describe('.forIn()', function() {
12 it('should loop through all properties in the object.', function(done) {
13 var obj = {a: 'foo', b: 'bar', c: 'baz'};
14 1 var values = [];
15 1 var keys = [];
16
17 1 forIn(obj, function(value, key, o) {
18 expect(o).to.equal(obj);
19 3 keys.push(key);
20 3 values.push(value);
21 });
22
23 1 expect(keys).to.equal(['a', 'b', 'c']);
24 1 expect(values).to.equal(['foo', 'bar', 'baz']);
25 1 done();
26 });
27
28 1 it('should break the loop early if `false` is returned.', function(done) {
29 var obj = {a: 'foo', b: 'bar', c: 'baz'};
30 1 var values = [];
31 1 var keys = [];
32
33 1 forIn(obj, function(value, key, o) {
34 if (key === 'b') {
35 1 return false;
36 }
37 1 keys.push(key);
38 1 values.push(value);
39 });
40
41 1 expect(keys).to.equal(['a']);
42 1 expect(values).to.equal(['foo']);
43 1 done();
44 });
45 });

spec/helpers/3p/utils/lib/forOwn.js

100%
22
22
0
Line Lint Hits Source
1 /*
2 * COPY of https://github.com/jonschlinkert/for-own/tree/0.1.5/index.js
3 */
4
5 1 const Code = require('code');
6 1 const Lab = require('lab');
7 1 const lab = exports.lab = Lab.script();
8 1 const expect = Code.expect;
9 1 const it = lab.it;
10 1 const describe = lab.describe;
11
12 1 const forOwn = require('../../../../../helpers/3p/utils/lib/forOwn');
13
14
15 1 describe('forOwn', function() {
16 it('should expose keys and values from the given object', function(done) {
17 var obj = {a: 'foo', b: 'bar', c: 'baz'};
18 1 var values = [];
19 1 var keys = [];
20
21 1 forOwn(obj, function(value, key, o) {
22 expect(o).to.equal(obj);
23 3 keys.push(key);
24 3 values.push(value);
25 });
26
27 1 expect(keys).to.equal(['a', 'b', 'c']);
28 1 expect(values).to.equal(['foo', 'bar', 'baz']);
29 1 done();
30 });
31 });

spec/helpers/3p/utils/lib/indexOf.js

100%
34
34
0
Line Lint Hits Source
1 /*!
2 * index-of <https://github.com/jonschlinkert/index-of>
3 *
4 * Copyright (c) 2014 Jon Schlinkert, contributors.
5 * Licensed under the MIT License
6 */
7
8 1 const Code = require('code');
9 1 const Lab = require('lab');
10 1 const lab = exports.lab = Lab.script();
11 1 const expect = Code.expect;
12 1 const it = lab.it;
13 1 const describe = lab.describe;
14
15 1 const indexOf = require('../../../../../helpers/3p/utils/lib/indexOf');
16
17 1 describe('indexOf', function () {
18 it('should get the index of the given element:', function (done) {
19 expect(indexOf(['a', 'b', 'c'], 'a')).to.equal(0);
20 1 expect(indexOf(['a', 'b', 'c'], 'b')).to.equal(1);
21 1 expect(indexOf(['a', 'b', 'c'], 'c')).to.equal(2);
22 1 done();
23 });
24
25 1 it('should return -1 if fromIndex is out of range:', function (done) {
26 expect(indexOf(['a', undefined, 'b', 'c', 'a'], undefined, 5)).to.equal(-1);
27 1 done();
28 });
29
30 1 it('should return -1 if the element does not exist:', function (done) {
31 expect(indexOf(['a', 'b', 'c'], 'd')).to.equal(-1);
32 1 done();
33 });
34
35 1 it('should return -1 if the array is undefined', function (done) {
36 expect(indexOf(undefined, 'd')).to.equal(-1);
37 1 done();
38 });
39
40 1 it('should get the index, starting from the given index:', function (done) {
41 expect(indexOf(['a', 'b', 'c', 'a', 'b', 'c'], 'b', 2)).to.equal(4);
42 1 expect(indexOf(['a', undefined, 'b', 'c', 'a'], undefined, 0)).to.equal(1);
43 1 expect(indexOf(['a', undefined, 'b', 'c', 'a'], undefined, 1)).to.equal(1);
44 1 expect(indexOf(['a', undefined, 'b', 'c', 'a'], undefined, 2)).to.equal(-1);
45 1 done();
46 });
47 });

spec/helpers/3p/utils/lib/isEven.js

100%
35
35
0
Line Lint Hits Source
1 /*!
2 * https://raw.githubusercontent.com/i-voted-for-trump/is-even/0.1.2/test.js
3 */
4
5 1 const Code = require('code');
6 1 const Lab = require('lab');
7 1 const lab = exports.lab = Lab.script();
8 1 const expect = Code.expect;
9 1 const it = lab.it;
10 1 const describe = lab.describe;
11
12 1 const isEven = require('../../../../../helpers/3p/utils/lib/isEven');
13
14 1 describe('isEven', function() {
15 it('should return true if the number is odd:', function(done) {
16 expect(isEven(0)).to.equal(true);
17 1 expect(!isEven(1)).to.equal(true);
18 1 expect(isEven(2)).to.equal(true);
19 1 expect(!isEven(3)).to.equal(true);
20 1 done();
21 });
22
23 1 it('should work with strings:', function(done) {
24 expect(isEven('0')).to.equal(true);
25 1 expect(!isEven('1')).to.equal(true);
26 1 expect(isEven('2')).to.equal(true);
27 1 expect(!isEven('3')).to.equal(true);
28 1 done();
29 });
30
31 1 it('should throw an error on bad args:', function(done) {
32 expect(function() {
33 isEven();
34 }, /expects a number\.$/).to.throw();
35 1 done();
36 });
37
38 1 it('should throw an error on non-integer args:', function(done) {
39 expect(function() {
40 isEven('1.1e0');
41 }, /expects an integer\.$/).to.throw();
42 1 done();
43 });
44 });

spec/helpers/3p/utils/lib/isExtendable.js

100%
29
29
0
Line Lint Hits Source
1 1 const Code = require('code');
2 1 const Lab = require('lab');
3 1 const lab = exports.lab = Lab.script();
4 1 const expect = Code.expect;
5 1 const it = lab.it;
6 1 const describe = lab.describe;
7
8 1 var isExtendable = require('../../../../../helpers/3p/utils/lib/isExtendable');
9
10
11 1 describe('isExtendable', function() {
12 it('should return true when a value is an object:', function(done) {
13 expect(isExtendable({})).to.equal(true);
14 1 expect(isExtendable([])).to.equal(true);
15 1 expect(isExtendable(function() {})).to.equal(true);
16 1 done();
17 });
18
19 1 it('should return false when a value is not an object:', function(done) {
20 expect(!isExtendable(new RegExp('foo'))).to.equal(true);
21 1 expect(!isExtendable(/foo/)).to.equal(true);
22 1 expect(!isExtendable(new Date())).to.equal(true);
23 1 expect(!isExtendable(new Error())).to.equal(true);
24 1 expect(!isExtendable('a')).to.equal(true);
25 1 expect(!isExtendable(5)).to.equal(true);
26 1 expect(!isExtendable(null)).to.equal(true);
27 1 expect(!isExtendable()).to.equal(true);
28 1 expect(!isExtendable(undefined)).to.equal(true);
29 1 expect(!isExtendable(true)).to.equal(true);
30 1 expect(!isExtendable(false)).to.equal(true);
31 1 done();
32 });
33 });

spec/helpers/3p/utils/lib/isNumber.js

100%
138
138
0
Line Lint Hits Source
1 1 const Code = require('code');
2 1 const Lab = require('lab');
3 1 const lab = exports.lab = Lab.script();
4 1 const expect = Code.expect;
5 1 const it = lab.it;
6 1 const describe = lab.describe;
7
8 1 const isNumber = require('../../../../../helpers/3p/utils/lib/isNumber');
9
10 1 describe('is a number', function() {
11 var fixtures = [
12 0xff,
13 5e3,
14 0,
15 0.1,
16 -0.1,
17 -1.1,
18 37,
19 3.14,
20
21 1,
22 1.1,
23 10,
24 10.1,
25 100,
26 -100,
27
28 '0.1',
29 '-0.1',
30 '-1.1',
31 '0',
32 '012',
33 '0xff',
34 '1',
35 '1.1',
36 '10',
37 '10.10',
38 '100',
39 '5e3',
40 ' 56\r\n ', // issue#3
41
42 Math.LN2,
43
44 // 012, Octal literal not allowed in strict mode
45 parseInt('012'),
46 parseFloat('012'),
47 Math.abs(1),
48 Math.acos(1),
49 Math.asin(1),
50 Math.atan(1),
51 Math.atan2(1, 2),
52 Math.ceil(1),
53 Math.cos(1),
54 Math.E,
55 Math.exp(1),
56 Math.floor(1),
57 Math.LN10,
58 Math.LN2,
59 Math.log(1),
60 Math.LOG10E,
61 Math.LOG2E,
62 Math.max(1, 2),
63 Math.min(1, 2),
64 Math.PI,
65 Math.pow(1, 2),
66 Math.pow(5, 5),
67 Math.random(1),
68 Math.round(1),
69 Math.sin(1),
70 Math.sqrt(1),
71 Math.SQRT1_2,
72 Math.SQRT2,
73 Math.tan(1),
74
75 Number.MAX_VALUE,
76 Number.MIN_VALUE,
77
78 '0.0',
79 '0x0',
80 '0e+5',
81 '000',
82 '0.0e-5',
83 '0.0E5',
84
85 +'',
86 +1,
87 +3.14,
88 +37,
89 +5,
90 +[],
91 +false,
92 +Math.LN2,
93 +true,
94 +null,
95 +new Date()
96 ];
97
98 1 fixtures.forEach(function(num, idx) {
99 it(JSON.stringify(num) + ' should be a number', function(done) {
100 expect(isNumber(num)).to.equal(true);
101 76 done();
102 });
103 });
104 });
105
106 1 describe('is not a number', function() {
107 var fixtures = [
108 ' ', // issue#3
109 '\r\n\t', // issue#3
110 '',
111 '',
112 '3a',
113 'abc',
114 'false',
115 'null',
116 'true',
117 'undefined',
118 +'abc',
119 +/foo/,
120 +[1, 2, 4],
121 +Infinity,
122 +Math.sin,
123 +NaN,
124 +undefined,
125 +{ a: 1 },
126 +{},
127 /foo/,
128 [1, 2, 3],
129 [1],
130 [],
131 true,
132 false,
133 +function() {},
134 function() {},
135 Infinity,
136 -Infinity,
137 Math.sin,
138 NaN,
139 new Date(),
140 null,
141 undefined,
142 {}
143 ];
144
145 1 fixtures.forEach(function(num) {
146 it(JSON.stringify(num) + ' should not be a number', function(done) {
147 expect(!isNumber(num)).to.equal(true);
148 35 done();
149 });
150 });
151 });

spec/helpers/3p/utils/lib/isOdd.js

91.43%
35
32
3
Line Lint Hits Source
1 1 const Code = require('code');
2 1 const Lab = require('lab');
3 1 const lab = exports.lab = Lab.script();
4 1 const expect = Code.expect;
5 1 const it = lab.it;
6 1 const describe = lab.describe;
7
8 1 const isOdd = require('../../../../../helpers/3p/utils/lib/isOdd');
9
10 1 describe('isOdd', function() {
11 it('should return true if the number is odd:', function(done) {
12 expect(!isOdd(0)).to.equal(true);
13 1 expect(!isOdd(2)).to.equal(true);
14 1 expect(isOdd(1)).to.equal(true);
15 1 expect(isOdd(3)).to.equal(true);
16 1 expect(isOdd(-1)).to.equal(true);
17 1 expect(isOdd(-3)).to.equal(true);
18 1 expect(isOdd(1.0e0)).to.equal(true);
19 1 expect(isOdd(9007199254740991)).to.equal(true);
20 1 done();
21 });
22
23 1 it('should work with strings:', function(done) {
24 expect(!isOdd('0')).to.equal(true);
25 1 expect(!isOdd('2')).to.equal(true);
26 1 expect(isOdd('1')).to.equal(true);
27 1 expect(isOdd('3')).to.equal(true);
28 1 expect(isOdd('1.0e0')).to.equal(true);
29 1 expect(isOdd('9007199254740991')).to.equal(true);
30 1 done();
31 });
32
33 1 it('should throw an error when an invalid value is passed', function(done) {
34
expect(() =>
isOdd()
, /expected a number/).to.throw();
35
expect(() =>
isOdd('foo')
, /expected a number/).to.throw();
36
expect(() =>
isOdd('1.1e0')
, /expected an integer/).to.throw();
37 // Not being thrown anymore on current Node version
38 // expect(() => isOdd('9007199254740992'), /value exceeds maximum safe integer/).to.throw();
39 // expect(() => isOdd(9007199254740992), /value exceeds maximum safe integer/).to.throw();
40 1 done();
41 });
42 });
43
44

spec/helpers/3p/utils/lib/isPlainObject.js

100%
28
28
0
Line Lint Hits Source
1 1 const Code = require('code');
2 1 const Lab = require('lab');
3 1 const lab = exports.lab = Lab.script();
4 1 const expect = Code.expect;
5 1 const it = lab.it;
6 1 const describe = lab.describe;
7
8 1 var isPlainObject = require('../../../../../helpers/3p/utils/lib/isPlainObject');
9
10 1 describe('Same-Realm Server Tests', function() {
11 it('should return `true` if the object is created by the `Object` constructor.', function(done) {
12 expect(isPlainObject(Object.create({}))).to.equal(true);
13 1 expect(isPlainObject(Object.create(Object.prototype))).to.equal(true);
14 1 expect(isPlainObject({foo: 'bar'})).to.equal(true);
15 1 expect(isPlainObject({})).to.equal(true);
16 1 expect(isPlainObject(Object.create(null))).to.equal(true);
17 1 done();
18 });
19
20 1 it('should return `false` if the object is not created by the `Object` constructor.', function(done) {
21 1 function Foo() {this.abc = {};};
22
23 1 expect(!isPlainObject(/foo/)).to.equal(true);
24 1 expect(!isPlainObject(function() {})).to.equal(true);
25 1 expect(!isPlainObject(1)).to.equal(true);
26 1 expect(!isPlainObject(['foo', 'bar'])).to.equal(true);
27 1 expect(!isPlainObject([])).to.equal(true);
28 1 expect(!isPlainObject(new Foo)).to.equal(true);
29 1 expect(!isPlainObject(null)).to.equal(true);
30 1 done();
31 });
32 });

spec/helpers/3p/utils/lib/kindOf.js

99.19%
123
122
1
Line Lint Hits Source
1 /*!
2 * kind-of <https://github.com/jonschlinkert/kind-of>
3 *
4 * Copyright (c) 2014-2015, Jon Schlinkert.
5 * Licensed under the MIT License
6 */
7
8 1 const Code = require('code');
9 1 const Lab = require('lab');
10 1 const lab = exports.lab = Lab.script();
11 1 const expect = Code.expect;
12 1 const it = lab.it;
13 1 const describe = lab.describe;
14
15 1 var kindOf = require('../../../../../helpers/3p/utils/lib/kindOf');
16
17 1 var version = process.version.match(/^.(\d+)\.(\d+)/);
18 1 var major = version[1];
19 1 var minor = version[2];
20
21 1 describe('kindOf', function () {
22 describe('null and undefined', function () {
23 it('should work for undefined', function (done) {
24 expect(kindOf(undefined)).to.equal('undefined');
25 1 done();
26 });
27
28 1 it('should work for null', function (done) {
29 expect(kindOf(null)).to.equal('null');
30 1 done();
31 });
32 });
33
34 1 describe('primitives', function () {
35 it('should work for booleans', function (done) {
36 expect(kindOf(true)).to.equal('boolean');
37 1 expect(kindOf(false)).to.equal('boolean');
38 1 expect(kindOf(new Boolean(true))).to.equal('boolean');
39 1 done();
40 });
41
42 1 it('should work for numbers', function (done) {
43 expect(kindOf(42)).to.equal('number');
44 1 expect(kindOf(new Number(42))).to.equal('number');
45 1 done();
46 });
47
48 1 it('should work for strings', function (done) {
49 expect(kindOf('str')).to.equal('string');
50 1 expect(kindOf(new String('str'))).to.equal('string');
51 1 done();
52 });
53 });
54
55 1 describe('objects', function () {
56 it('should work for arguments', function (done) {
57 (function () {
58 expect(kindOf(arguments)).to.equal('arguments');
59 1 done();
60 })();
61 });
62
63 1 it('should work for buffers', function (done) {
64 expect(kindOf(new Buffer(''))).to.equal('buffer');
65 1 done();
66 });
67
68 1 it('should work for objects', function (done) {
69 function Test() {}
70 1 var instance = new Test();
71 1 var literal = {};
72 1 var create = Object.create(null);
73
74 1 expect(kindOf(instance)).to.equal('object');
75 1 expect(kindOf(literal)).to.equal('object');
76 1 expect(kindOf(create)).to.equal('object');
77 1 done();
78 });
79
80 1 it('should work for dates', function (done) {
81 expect(kindOf(new Date())).to.equal('date');
82 1 done();
83 });
84
85 1 it('should work for arrays', function (done) {
86 expect(kindOf([])).to.equal('array');
87 1 expect(kindOf([1, 2, 3])).to.equal('array');
88 1 expect(kindOf(new Array())).to.equal('array');
89 1 done();
90 });
91
92 1 it('should work for regular expressions', function (done) {
93 expect(kindOf(/[\s\S]+/)).to.equal('regexp');
94 1 expect(kindOf(new RegExp('^' + 'foo$'))).to.equal('regexp');
95 1 done();
96 });
97
98 1 it('should work for functions', function (done) {
99 expect(kindOf(function () {})).to.equal('function');
100 1 expect(kindOf(new Function())).to.equal('function');
101 1 done();
102 });
103 });
104
if (
major > 0
||
minor > 11
) {
105 1 describe('es6 features', function () {
106 it('should work for Map', function (done) {
107 var map = new Map();
108 1 expect(kindOf(map)).to.equal('map');
109 1 expect(kindOf(map.set)).to.equal('function');
110 1 expect(kindOf(map.get)).to.equal('function');
111 1 expect(kindOf(map.add)).to.equal('undefined');
112 1 done();
113 });
114
115 1 it('should work for WeakMap', function (done) {
116 var weakmap = new WeakMap();
117 1 expect(kindOf(weakmap)).to.equal('weakmap');
118 1 expect(kindOf(weakmap.set)).to.equal('function');
119 1 expect(kindOf(weakmap.get)).to.equal('function');
120 1 expect(kindOf(weakmap.add)).to.equal('undefined');
121 1 done();
122 });
123
124 1 it('should work for Set', function (done) {
125 var set = new Set();
126 1 expect(kindOf(set)).to.equal('set');
127 1 expect(kindOf(set.add)).to.equal('function');
128 1 expect(kindOf(set.set)).to.equal('undefined');
129 1 expect(kindOf(set.get)).to.equal('undefined');
130 1 done();
131 });
132
133 1 it('should work for WeakSet', function (done) {
134 var weakset = new WeakSet();
135 1 expect(kindOf(weakset)).to.equal('weakset');
136 1 expect(kindOf(weakset.add)).to.equal('function');
137 1 expect(kindOf(weakset.set)).to.equal('undefined');
138 1 expect(kindOf(weakset.get)).to.equal('undefined');
139 1 done();
140 });
141
142 1 it('should work for Symbol', function (done) {
143 expect(kindOf(Symbol('foo'))).to.equal('symbol');
144 1 expect(kindOf(Symbol.prototype)).to.equal('symbol');
145 1 done();
146 });
147 });
148 }
149 });

spec/helpers/3p/utils/lib/makeIterator.js

100%
57
57
0
Line Lint Hits Source
1 1 const Code = require("code");
2 1 const Lab = require("lab");
3 1 const lab = (exports.lab = Lab.script());
4 1 const expect = Code.expect;
5 1 const it = lab.it;
6 1 const describe = lab.describe;
7
8 1 var makeIterator = require("../../../../../helpers/3p/utils/lib/makeIterator");
9
10 1 describe('make iterator', function() {
11 it('should return source argument if it is already a function with no context', function(done) {
12 var fn = function() {};
13 1 expect(makeIterator(fn)).to.equal(fn);
14 1 done();
15 });
16
17 1 it('should return a function that calls object/deepMatches if argument is an object', function(done) {
18 var fn = makeIterator({ a: 1, b: { c: 2 } });
19 1 expect(fn({ a: 1, b: { c: 2, d: 3 } })).to.equal(true);
20 1 expect(fn({ a: 1, b: { c: 3 } })).to.equal(false);
21 1 done();
22 });
23
24 1 it('should return a function that calls object/deepMatches if argument is a regex', function(done) {
25 expect(makeIterator(/[a-c]/)(['a', 'b', 'c', 'd'])).to.equal(true);
26 1 expect(makeIterator(/[m-z]/)(['a', 'b', 'c', 'd'])).to.equal(false);
27 1 done();
28 });
29
30 1 it('should return a function that returns the property value if argument is a string', function(done) {
31 var fn = makeIterator('a');
32 1 expect(fn({a:1,b:2})).to.equal(1);
33 1 expect(fn({a:2,b:2})).to.equal(2);
34 1 done();
35 });
36
37 1 it('should return a function that returns the property value if argument is a number', function(done) {
38 var fn = makeIterator(1);
39 1 expect(fn([0,4,5])).to.equal(4);
40 1 expect(fn([6,7,8])).to.equal(7);
41 1 done();
42 });
43
44 1 it('should return an identify function if no args', function(done) {
45 var fn = makeIterator();
46 1 expect(fn(null)).to.equal(null);
47 1 expect(fn(void(0))).to.equal(void(0));
48 1 expect(fn(3)).to.equal(3);
49 1 done();
50 });
51
52 1 it('should return an identify function if first arg is `null`', function(done) {
53 var fn = makeIterator(null);
54 1 expect(fn(null)).to.equal(null);
55 1 expect(fn(void(0))).to.equal(void(0));
56 1 expect(fn(3)).to.equal(3);
57 1 done();
58 });
59
60 1 it('should return a function that is called with the specified context', function(done) {
61 var context = {};
62 1 var iterator = makeIterator(function() { return this; }, context);
63 1 expect(iterator()).to.equal(context);
64 1 done();
65 });
66 });

spec/helpers/3p/utils/lib/markdown.js

96.43%
112
108
4
Line Lint Hits Source
1 1 const Code = require("code");
2 1 const Lab = require("lab");
3 1 const lab = (exports.lab = Lab.script());
4 1 const expect = Code.expect;
5 1 const it = lab.it;
6 1 const describe = lab.describe;
7
8 1 const markdown = require("../../../../../helpers/3p/utils/lib/markdown");
9
10 1 const {buildRenderer} = require("../../../../spec-helpers");
11 1 const renderer = buildRenderer();
12 1 const hbs = renderer.handlebars;
13
14 1 const hljs = require("highlight.js");
15
16 function highlight(code, language) {
17 4 try {
18 4 try {
19 4 return hljs.highlight(code, {language}).value;
20 } catch (err) {
21 if (!/Unknown language/i.test(err.message)) {
22 throw err;
23 }
24 return hljs.highlightAuto(code).value;
25 }
26 } catch (err) {
27 return code;
28 }
29 }
30
31 1 describe("sync", function () {
32 describe("markdown helper", function () {
33 it("should render markdown:", function (done) {
34 expect(markdown("# heading")).to.equal("<h1>heading</h1>\n");
35 1 done();
36 });
37
38 1 it("should highlight code blocks", function (done) {
39 const html = markdown('```js\nvar foo = "bar";\n```\n', {
40 highlight: highlight,
41 });
42 1 expect(html).to.equal(
43 '<pre><code class="language-js"><span class="hljs-keyword">var</span> foo = <span class="hljs-string">&quot;bar&quot;</span>;\n</code></pre>\n'
44 );
45 1 done();
46 });
47
48 1 it("should pass options to remarkable", function (done) {
49 const a = markdown("abc https://github.com/jonschlinkert/remarkable xyz", {
50 highlight: highlight,
51 linkify: true,
52 });
53 1 expect(a).to.equal(
54 '<p>abc <a href="https://github.com/jonschlinkert/remarkable">https://github.com/jonschlinkert/remarkable</a> xyz</p>\n'
55 );
56
57 1 const b = markdown("abc https://github.com/jonschlinkert/remarkable xyz", {
58 highlight: highlight,
59 linkify: false,
60 });
61 1 expect(b).to.equal(
62 "<p>abc https://github.com/jonschlinkert/remarkable xyz</p>\n"
63 );
64 1 done();
65 });
66
67 1 it("should pass options to highlight.js:", function (done) {
68 const html = markdown('```js\nvar foo = "bar";\n```\n', {
69 highlight: highlight,
70 langPrefix: "language-",
71 });
72 1 expect(html).to.equal(
73 '<pre><code class="language-js"><span class="hljs-keyword">var</span> foo = <span class="hljs-string">&quot;bar&quot;</span>;\n</code></pre>\n'
74 );
75 1 done();
76 });
77 });
78
79 1 describe("handlebars:", function () {
80 it("should work as a handlebars helper:", function (done) {
81 hbs.registerHelper("markdown", markdown({highlight: highlight}));
82 1 expect(
83 hbs.compile("{{#markdown}}# {{title}}{{/markdown}}")({
84 title: "heading",
85 })
86 ).to.equal("<h1>heading</h1>\n");
87 1 done();
88 });
89
90 1 it("should pass hash options to remarkable:", function (done) {
91 hbs.registerHelper("markdown", markdown({highlight: highlight}));
92
93 // `linkify: true`
94 1 const a = hbs.compile(
95 "{{#markdown linkify=true}}abc https://github.com/jonschlinkert/remarkable xyz{{/markdown}}"
96 )();
97 1 expect(a).to.equal(
98 '<p>abc <a href="https://github.com/jonschlinkert/remarkable">https://github.com/jonschlinkert/remarkable</a> xyz</p>\n'
99 );
100
101 // `linkify: false`
102 1 const b = hbs.compile(
103 "{{#markdown linkify=false}}abc https://github.com/jonschlinkert/remarkable xyz{{/markdown}}"
104 )();
105 1 expect(b).to.equal(
106 "<p>abc https://github.com/jonschlinkert/remarkable xyz</p>\n"
107 );
108 1 done();
109 });
110
111 1 it("should pass hash options to highlight.js:", function (done) {
112 hbs.registerHelper("markdown", markdown({highlight: highlight}));
113
114 // `langPrefix = language-`
115 1 const a = hbs.compile(
116 '{{#markdown}}```js\nvar foo = "bar";\n```\n{{/markdown}}'
117 )();
118 1 expect(a).to.equal(
119 '<pre><code class="language-js"><span class="hljs-keyword">var</span> foo = <span class="hljs-string">&quot;bar&quot;</span>;\n</code></pre>\n'
120 );
121
122 // `langPrefix = language-`
123 1 const b = hbs.compile(
124 '{{#markdown langPrefix="language-"}}```js\nvar foo = "bar";\n```\n{{/markdown}}'
125 )();
126 1 expect(b).to.equal(
127 '<pre><code class="language-js"><span class="hljs-keyword">var</span> foo = <span class="hljs-string">&quot;bar&quot;</span>;\n</code></pre>\n'
128 );
129 1 done();
130 });
131 });
132 });
133

spec/helpers/3p/utils/lib/mixinDeep.js

100%
110
110
0
Line Lint Hits Source
1 1 const Code = require('code');
2 1 const Lab = require('lab');
3 1 const lab = exports.lab = Lab.script();
4 1 const expect = Code.expect;
5 1 const it = lab.it;
6 1 const describe = lab.describe;
7
8 1 var mixinDeep = require('../../../../../helpers/3p/utils/lib/mixinDeep');
9
10
11 1 describe('.mixinDeep()', function() {
12 it('should deeply mix the properties of object into the first object.', function(done) {
13 var a = mixinDeep({a: {aa: 'aa'} }, {a: {bb: 'bb'} }, {a: {cc: 'cc'} });
14 1 expect(a).to.equal({a: {aa: 'aa', bb: 'bb', cc: 'cc'} });
15 1 var b = mixinDeep({a: {aa: 'aa', dd: {ee: 'ff'} } }, {a: {bb: 'bb', dd: {gg: 'hh'} } }, {a: {cc: 'cc', dd: {ii: 'jj'} } });
16 1 expect(b).to.equal({a: {aa: 'aa', dd: {ee: 'ff', gg: 'hh', ii: 'jj'}, bb: 'bb', cc: 'cc'} });
17 1 done();
18 });
19
20 1 it('should merge object properties without affecting any object', function(done) {
21 var obj1 = {a: 0, b: 1};
22 1 var obj2 = {c: 2, d: 3};
23 1 var obj3 = {a: 4, d: 5};
24
25 1 var actual = {a: 4, b: 1, c: 2, d: 5 };
26
27 1 expect(mixinDeep({}, obj1, obj2, obj3)).to.equal(actual);
28 1 expect(actual).to.not.equal(obj1);
29 1 expect(actual).to.not.equal(obj2);
30 1 expect(actual).to.not.equal(obj3);
31 1 done();
32 });
33
34 1 it('should do a deep merge', function(done) {
35 var obj1 = {a: {b: 1, c: 1, d: {e: 1, f: 1}}};
36 1 var obj2 = {a: {b: 2, d : {f : 'f'} }};
37
38 1 expect(mixinDeep(obj1, obj2)).to.equal({a: {b: 2, c: 1, d: {e: 1, f: 'f'} }});
39 1 done();
40 });
41
42 1 it('should use the last value defined', function(done) {
43 var obj1 = {a: 'b'};
44 1 var obj2 = {a: 'c'};
45
46 1 expect(mixinDeep(obj1, obj2)).to.equal({a: 'c'});
47 1 done();
48 });
49
50 1 it('should use the last value defined on nested object', function(done) {
51 var obj1 = {a: 'b', c: {d: 'e'}};
52 1 var obj2 = {a: 'c', c: {d: 'f'}};
53
54 1 expect(mixinDeep(obj1, obj2)).to.equal({a: 'c', c: {d: 'f'}});
55 1 done();
56 });
57
58 1 it('should shallow clone when an empty object is passed', function(done) {
59 var obj1 = {a: 'b', c: {d: 'e'}};
60 1 var obj2 = {a: 'c', c: {d: 'f'}};
61
62 1 var res = mixinDeep({}, obj1, obj2);
63 1 expect(res).to.equal({a: 'c', c: {d: 'f'}});
64 1 done();
65 });
66
67 1 it('should merge additional objects into the first:', function(done) {
68 var obj1 = {a: {b: 1, c: 1, d: {e: 1, f: 1}}};
69 1 var obj2 = {a: {b: 2, d : {f : 'f'} }};
70
71 1 mixinDeep(obj1, obj2);
72 1 expect(obj1).to.equal({a: {b: 2, c: 1, d: {e: 1, f: 'f'} }});
73 1 done();
74 });
75
76 1 it('should clone objects during merge', function(done) {
77 var obj1 = {a: {b :1}};
78 1 var obj2 = {a: {c :2}};
79
80 1 var actual = mixinDeep({}, obj1, obj2);
81 1 expect(actual).to.equal({a:{b:1, c:2}});
82 1 expect(actual.a).to.equal(obj1.a);
83 1 expect(actual.a).to.not.equal(obj2.a);
84 1 done();
85 });
86
87 1 it('should deep clone arrays during merge', function(done) {
88 var obj1 = {a: [1, 2, [3, 4]]};
89 1 var obj2 = {b : [5, 6]};
90
91 1 var actual = mixinDeep(obj1, obj2);
92 1 expect(actual.a).to.equal([1, 2, [3, 4]]);
93 1 expect(actual.a[2]).to.equal([3, 4]);
94 1 expect(actual.b).to.equal(obj2.b);
95 1 done();
96 });
97
98 1 it('should copy source properties', function(done) {
99 expect(mixinDeep({ test: true }).test).to.equal(true);
100 1 done();
101 });
102
103 1 it('should not clone arrays', function(done) {
104 expect(mixinDeep([1, 2, 3])).to.equal([1, 2, 3]);
105 1 expect(mixinDeep([1, 2, 3], {})).to.equal([1, 2, 3]);
106 1 done();
107 });
108
109 1 it('should work with sparse objects:', function(done) {
110 var actual = mixinDeep({}, undefined, {a: 'b'}, undefined, {c: 'd'});
111 1 expect(actual).to.equal({a: 'b', c: 'd'});
112 1 done();
113 });
114
115 1 it('should clone RegExps', function(done) {
116 var fixture = /test/g;
117 1 var actual = mixinDeep(fixture);
118 1 expect(actual).to.equal(fixture);
119 1 done();
120 });
121
122 1 it('should clone Dates', function(done) {
123 var fixture = new Date();
124 1 var actual = mixinDeep(fixture);
125 1 expect(actual).to.equal(fixture);
126 1 done();
127 });
128
129 1 it('should not clone objects created with custom constructor', function(done) {
130 function TestType() { }
131 1 var fixture = new TestType();
132 1 var actual = mixinDeep(fixture);
133 1 expect(actual).to.equal(fixture);
134 1 done();
135 });
136 });

spec/helpers/3p/utils/lib/striptags.js

100%
109
109
0
Line Lint Hits Source
1 1 const Code = require('code');
2 1 const Lab = require('lab');
3 1 const lab = exports.lab = Lab.script();
4 1 const expect = Code.expect;
5 1 const it = lab.it;
6 1 const describe = lab.describe;
7
8 1 const striptags = require('../../../../../helpers/3p/utils/lib/striptags');
9
10 1 describe('striptags', function() {
11 it('should not modify plain text', function(done) {
12 var text = 'lorem ipsum < a>';
13
14 1 expect(striptags(text)).to.equal(text);
15 1 done();
16 });
17
18 1 it('should remove simple HTML tags', function(done) {
19 var html = '<a href="">lorem <strong>ipsum</strong></a>',
20 text = 'lorem ipsum';
21
22 1 expect(striptags(html)).to.equal(text);
23 1 done();
24 });
25
26 1 it('should leave HTML tags if specified', function(done) {
27 var html = '<strong>lorem ipsum</strong>',
28 allowedTags = '<strong>';
29
30 1 expect(striptags(html, allowedTags)).to.equal(html);
31 1 done();
32 });
33
34 1 it('should leave attributes when allowing HTML', function(done) {
35 var html = '<a href="https://example.com">lorem ipsum</a>',
36 allowedTags = '<a>';
37
38 1 expect(striptags(html, allowedTags)).to.equal(html);
39 1 done();
40 });
41
42 1 it('should leave nested HTML tags if specified', function(done) {
43 var html = '<div>lorem <strong>ipsum</strong></div>',
44 strippedHtml = 'lorem <strong>ipsum</strong>',
45 allowedTags = '<strong>';
46
47 1 expect(striptags(html, allowedTags)).to.equal(strippedHtml);
48 1 done();
49 });
50
51 1 it('should leave outer HTML tags if specified', function(done) {
52 var html = '<div>lorem <strong>ipsum</strong></div>',
53 strippedHtml = '<div>lorem ipsum</div>',
54 allowedTags = '<div>';
55
56 1 expect(striptags(html, allowedTags)).to.equal(strippedHtml);
57 1 done();
58 });
59
60 1 it('should remove DOCTYPE declaration', function(done) {
61 var html = '<!DOCTYPE html> lorem ipsum',
62 text = ' lorem ipsum';
63
64 1 expect(striptags(html)).to.equal(text);
65 1 done();
66 });
67
68 1 it('should remove comments', function(done) {
69 var html = '<!-- lorem ipsum --> dolor sit amet',
70 text = ' dolor sit amet';
71
72 1 expect(striptags(html)).to.equal(text);
73 1 done();
74 });
75
76 1 it('should strip <> within quotes', function(done) {
77 var html = '<a href="<script>">lorem ipsum</a>',
78 strippedHtml = '<a href="script">lorem ipsum</a>',
79 allowedTags = '<a>';
80
81 1 expect(striptags(html, allowedTags)).to.equal(strippedHtml);
82 1 done();
83 });
84
85 1 it('should strip extra < within tags', function(done) {
86 var html = '<div<>>lorem ipsum</div>',
87 strippedHtml = '<div>lorem ipsum</div>',
88 allowedTags = '<div>';
89
90 1 expect(striptags(html, allowedTags)).to.equal(strippedHtml);
91 1 done();
92 });
93
94 1 it('should strip tags within comments', function(done) {
95 var html = '<!-- <strong>lorem ipsum</strong> --> dolor sit',
96 text = ' dolor sit';
97
98 1 expect(striptags(html)).to.equal(text);
99 1 done();
100 });
101
102 1 it('should strip comment-like tags', function(done) {
103 var html = '<! lorem ipsum> dolor sit',
104 text = ' dolor sit';
105
106 1 expect(striptags(html)).to.equal(text);
107 1 done();
108 });
109
110 1 it('should leave normal exclamation points alone', function(done) {
111 var text = 'lorem ipsum! dolor sit amet';
112
113 1 expect(striptags(text)).to.equal(text);
114 1 done();
115 });
116
117 1 it('should allow an array parameter for allowable tags', function(done) {
118 var html = '<strong>lorem <em>ipsum</em></strong>',
119 allowedTags = ['strong', 'em'];
120
121 1 expect(striptags(html, allowedTags)).to.equal(html);
122 1 done();
123 });
124
125 1 it('should strip tags when an empty array is provided', function(done) {
126 var html = '<article>lorem <a href="#">ipsum</a></article>',
127 allowedTags = [],
128 text = 'lorem ipsum';
129
130 1 expect(striptags(html, allowedTags)).to.equal(text);
131 1 done();
132 });
133
134 1 it('should not fail with nested quotes', function(done) {
135 var html = '<article attr="foo \'bar\'">lorem</article> ipsum',
136 allowedTags = [],
137 text = 'lorem ipsum';
138
139 1 expect(striptags(html, allowedTags)).to.equal(text);
140 1 done();
141 });
142 });

spec/helpers/lib/common.js

100%
115
115
0
Line Lint Hits Source
1 1 const getValue = require('../../../helpers/lib/common').getValue;
2 1 const appendLossyParam = require('../../../helpers/lib/common').appendLossyParam;
3 1 const Code = require('code'),
4 expect = Code.expect;
5 1 const Lab = require('lab'),
6 lab = exports.lab = Lab.script(),
7 describe = lab.experiment,
8 it = lab.it;
9 1 const Handlebars = require('handlebars');
10
11 1 describe('common utils', function () {
12 describe('getValue', function () {
13 const globals = {handlebars: Handlebars};
14 1 const obj = {
15 a: {
16 a: [{x: 'a'}, {y: 'b'}],
17 b: {
18 b: {
19 a: 1,
20 },
21 },
22 c: [1, 1, 2, 3, 5, 8, 13, 21, 34],
23 },
24 b: [2, 3, 5, 7, 11, 13, 17, 19],
25 c: 3,
26 d: false,
27 '42': 42,
28 };
29 1 obj.__proto__ = {x: 'yz'};
30
31 1 it('should get a value from an object', (done) => {
32 expect(getValue(obj, 'c', globals)).to.equal(3);
33 1 expect(getValue(obj, 'd', globals)).to.equal(false);
34 1 done();
35 });
36
37 1 it('should get nested values', (done) => {
38 expect(getValue(obj, 'a.b.b.a', globals)).to.equal(1);
39 1 expect(getValue(obj, ['a', 'b', 'b', 'a'], globals)).to.equal(1);
40 1 done();
41 });
42
43 1 it('should get nested values from arrays', (done) => {
44 expect(getValue(obj, 'b.0', globals)).to.equal(2);
45 1 expect(getValue(obj, 'a.c.5', globals)).to.equal(8);
46 1 done();
47 });
48
49 1 it('should get nested values from objects in arrays', (done) => {
50 expect(getValue(obj, 'a.a.1.y', globals)).to.equal('b');
51 1 done();
52 });
53
54 1 it('should return obj[String(path)] or undefined if path is not a string or array', (done) => {
55 expect(getValue(obj, {a: 1}, globals)).to.equal(undefined);
56 1 expect(getValue(obj, ()=>1, globals)).to.equal(undefined);
57 1 expect(getValue(obj, 42, globals)).to.equal(42);
58 1 done();
59 });
60
61 1 it('should return the whole object if path is empty', (done) => {
62 expect(getValue(obj, [], globals)).to.equal(obj);
63 1 done();
64 });
65
66 1 it('should return obj if path is falsey', (done) => {
67 expect(getValue(obj, '', globals)).to.equal(obj);
68 1 expect(getValue(obj, false, globals)).to.equal(obj);
69 1 expect(getValue(obj, 0, globals)).to.equal(obj);
70 1 done();
71 });
72
73 1 it('should return undefined if prop does not exist', (done) => {
74 expect(getValue(obj, 'a.a.a.a', globals)).to.equal(undefined);
75 1 expect(getValue(obj, 'a.c.23', globals)).to.equal(undefined);
76 1 expect(getValue(obj, 'ab', globals)).to.equal(undefined);
77 1 expect(getValue(obj, 'nonexistent', globals)).to.equal(undefined);
78 1 expect(getValue([ undefined ], '0.x', globals)).to.equal(undefined);
79 1 expect(getValue([ null ], '0.x', globals)).to.equal(undefined);
80 1 done();
81 });
82
83 1 it('should treat backslash-escaped . characters as part of a prop name', (done) => {
84 const data = {'a.b': {'c.d.e': 42, z: 'xyz'}};
85
86 1 expect(getValue(data, 'a\\.b.z', globals)).to.equal('xyz');
87 1 expect(getValue(data, 'a\\.b.c\\.d\\.e', globals)).to.equal(42);
88 1 done()
89 });
90
91 1 it('should not access inherited props', (done) => {
92 expect(getValue(obj, 'x', globals)).to.equal(undefined);
93 1 expect(getValue(obj, 'a.constructor', globals)).to.equal(undefined);
94 1 done();
95 });
96 });
97
98 1 describe('appendLossyParam', function() {
99 it('should append compression=lossy to URLs without query params', function(done) {
100 const result = appendLossyParam('https://example.com/image.jpg', true);
101 1 expect(result).to.equal('https://example.com/image.jpg?compression=lossy');
102 1 done();
103 });
104
105 1 it('should append compression=lossy to URLs with existing query params', function(done) {
106 const result = appendLossyParam('https://example.com/image.jpg?c=2', true);
107 1 expect(result).to.equal('https://example.com/image.jpg?c=2&compression=lossy');
108 1 done();
109 });
110
111 1 it('should not modify URL when lossy is false', function(done) {
112 const url = 'https://example.com/image.jpg?c=2';
113 1 const result = appendLossyParam(url, false);
114 1 expect(result).to.equal(url);
115 1 done();
116 });
117
118 1 it('should not modify URL when lossy is undefined', function(done) {
119 const url = 'https://example.com/image.jpg';
120 1 const result = appendLossyParam(url);
121 1 expect(result).to.equal(url);
122 1 done();
123 });
124
125 1 it('should not modify URL when lossy is not a boolean', function(done) {
126 const url = 'https://example.com/image.jpg';
127 1 const result = appendLossyParam(url, 'true');
128 1 expect(result).to.equal(url);
129 1 done();
130 });
131 });
132 });

spec/helpers/lib/resourceHints.js

97.53%
162
158
4
Line Lint Hits Source
1 1 const Code = require('code'),
2 expect = Code.expect;
3 1 const Lab = require('lab'),
4 lab = exports.lab = Lab.script(),
5 describe = lab.experiment,
6 it = lab.it;
7 1 const {
8 addResourceHint,
9 defaultResourceHintState,
10 resourceHintAllowedTypes
11 } = require('../../../helpers/lib/resourceHints');
12
13 1 describe('resource hints', function () {
14
15 describe('addResourceHint', function () {
16
17 it("creates resource hints when valid params are provided", (done) => {
18 const globals = {resourceHints: []};
19
20 1 const src = '/my/styles.css';
21 1 const type = 'style';
22 1 const state = 'preload';
23 1 const cors = 'anonymous';
24 1 const expected = {src, state, type, cors};
25
26 1 addResourceHint(globals, src, state, type, cors);
27
28 1 expect(globals.resourceHints).to.have.length(1);
29 1 expect(globals.resourceHints[0]).to.equals(expected);
30
31 1 done();
32 });
33
34 1 it("creates resource hints when valid params are provided defaulting cors to no when no provided", (done) => {
35 const globals = {};
36
37 1 const src = '/my/styles.css';
38 1 const type = 'style';
39 1 const state = 'preload';
40 1 const expected = {src, state, type, cors: 'no'};
41
42 1 addResourceHint(globals, src, state, type);
43
44 1 expect(globals.resourceHints).to.have.length(1);
45 1 expect(globals.resourceHints[0]).to.equals(expected);
46
47 1 done();
48 });
49
50 1 it('does not add a hint when provided path is invalid', (done) => {
51 const globals = {resourceHints: []};
52
53 1 const throws = [undefined, null, ''].map(s => {
54 return () => {
55 addResourceHint(
56 global,
57 s,
58 defaultResourceHintState,
59 resourceHintAllowedTypes.resourceHintStyleType
60 );
61 };
62 });
63
64 1 throws.forEach(t => {
65 expect(t).to.throw();
66 });
67
68 1 expect(globals.resourceHints).to.have.length(0);
69
70 1 done();
71 });
72
73 1 it('does create a hint when no type is provided', (done) => {
74 const globals = {resourceHints: []};
75
76 1 addResourceHint(
77 globals,
78 'https://my.asset.css',
79 defaultResourceHintState
80 );
81
82 1 expect(globals.resourceHints).to.have.length(1);
83 1 expect(globals.resourceHints[0].type).to.not.exist();
84
85 1 done();
86 });
87
88 1 it('does not create a hint when provided state param is not supported', (done) => {
89 const globals = {resourceHints: []};
90
91
const f = () =>
addResourceHint(
92 globals,
93 '/styles.css',
94 'not-supported'
95
)
;
96
97 1 expect(f).to.throw();
98
99 1 done();
100 });
101
102 1 it('does not create duplicate, by src, hints, and returns the found src', (done) => {
103 const globals = {resourceHints: []};
104
105 1 const src = '/my/styles.css';
106 1 const type = 'style';
107 1 const state = 'preload';
108
109 1 const first = addResourceHint(
110 globals,
111 src,
112 state,
113 type
114 );
115
116 1 for (let i = 0; i < 5; i++) {
117 5 const found = addResourceHint(
118 globals,
119 src,
120 state,
121 type
122 );
123 5 expect(found).to.equals(first);
124 }
125
126 1 expect(globals.resourceHints).to.have.length(1);
127
128 1 done();
129 });
130
131 1 it('does not create any hint when the limit of allowed hints was reached', (done) => {
132 const filled = Array(50).fill(1);
133 1 const globals = {resourceHints: filled};
134
135 1 const path = '/my/styles.css';
136 1 const returned = addResourceHint(
137 globals,
138 path,
139 'preload',
140 'style'
141 );
142
143 1 expect(globals.resourceHints).to.have.length(filled.length);
144 1 expect(returned).to.equals(path);
145 1 done();
146 });
147
148 1 it('should throw when invalid cors value is provided', done => {
149
const f = () =>
addResourceHint({}, '/theme.css', 'style', 'preload', 'invalid-cors')
;
150 1 expect(f).to.throw();
151 1 done();
152 });
153
154 1 it('should handle URLs with special chars', done => {
155 const data = [
156 {
157 original: "https://bigcommerce.paystackintegrations.com/js/script.js?store_hash=cag1k6vhir&installed_at=Tue May 10 2022 08:57:26 GMT+0000 (Coordinated Universal Time)",
158 expected: "https://bigcommerce.paystackintegrations.com/js/script.js?store_hash=cag1k6vhir&installed_at=Tue%20May%2010%202022%2008:57:26%20GMT+0000%20(Coordinated%20Universal%20Time)"
159 },
160 {
161 original: `https://instocknotify.blob.core.windows.net/stencil/cfa17df4-72e5-4c23-a4c7-1fa6903bdeb4.js?ts=17443383" type="text/javascript""`,
162 expected: "https://instocknotify.blob.core.windows.net/stencil/cfa17df4-72e5-4c23-a4c7-1fa6903bdeb4.js?ts=17443383%22%20type=%22text/javascript%22%22"
163 },
164 {
165 original: `https://instocknotify.blob.core.windows.net/stencil/41f9521c-57b6-4920-827b-77ba3477fcb5.js?ts=34911865" type="text/javascript"></script> <!--End InStockNotify Stencil Script -->`,
166 expected: "https://instocknotify.blob.core.windows.net/stencil/41f9521c-57b6-4920-827b-77ba3477fcb5.js?ts=34911865%22%20type=%22text/javascript%22%3E%3C/script%3E%20%3C!--End%20InStockNotify%20Stencil%20Script%20--%3E"
167 },
168 {
169 original: `https://fonts.googleapis.com/css?family=Karla:400|Montserrat:400,500,700&display=block`,
170 expected: `https://fonts.googleapis.com/css?family=Karla:400%7CMontserrat:400,500,700&display=block`
171 },
172 {
173 original: '/relative/asset/background.png?why=not',
174 expected: '/relative/asset/background.png?why=not'
175 }
176 ];
177 1 const globals = {resourceHints: []};
178
179 1 data.forEach(pair => {
180 const {original, expected} = pair;
181
182 5 const type = 'style';
183 5 const state = 'preload';
184 5 const cors = 'anonymous';
185
186 5 expect(
187 expected,
188 addResourceHint(globals, original, state, type, cors)
189 );
190 });
191 1 done();
192 });
193
194 1 it('should throw when invalid URL is passed in', done => {
195 const globals = {resourceHints: []};
196 1 const invalid = '';
197
const f = () =>
addResourceHint(globals, invalid, 'style', 'preload', 'no')
198 1 expect(f).to.throw();
199 1 done();
200 });
201 });
202 });
203

spec/lib/appError.js

100%
86
86
0
Line Lint Hits Source
1 'use strict';
2
3 1 const Code = require('code');
4 1 const Lab = require('lab');
5 1 const lab = exports.lab = Lab.script();
6 1 const expect = Code.expect;
7 1 const it = lab.it;
8 1 const describe = lab.describe;
9 1 const helpers = require('../spec-helpers');
10
11 1 const AppError = require('../../lib/appError');
12 class CustomError extends AppError {};
13
14 1 describe('AppError', () => {
15 it('is an instance of Error', done => {
16 try {
17 1 throw new AppError();
18 } catch(e) {
19 1 expect(e instanceof Error).to.be.true();
20 }
21 1 done();
22 });
23
24 1 it('keeps track of the message', done => {
25 const msg = helpers.randomString();
26 1 try {
27 1 throw new AppError(msg);
28 } catch(e) {
29 1 expect(e.message).to.equal(msg);
30 }
31 1 done();
32 });
33
34 1 it('keeps track of the extra details', done => {
35 const msg = helpers.randomString();
36 1 const data = helpers.randomString();
37 1 try {
38 1 throw new AppError(msg, { data });
39 } catch(e) {
40 1 expect(e.details.data).to.equal(data);
41 }
42 1 done();
43 });
44
45 1 it('supplies default details hash', done => {
46 const msg = helpers.randomString();
47 1 try {
48 1 throw new AppError(msg);
49 } catch(e) {
50 1 expect(e.details).to.equal({});
51 }
52 1 done();
53 });
54
55 1 it('makes the error name available', done => {
56 try {
57 1 throw new AppError();
58 } catch(e) {
59 1 expect(e.name).to.equal('AppError');
60 }
61 1 done();
62 });
63
64 1 it('does not include AppError constructor in stack trace', done => {
65 const bug = () => {
66 throw new AppError();
67 };
68
69 1 try {
70 1 bug();
71 } catch(e) {
72 1 expect(e.stack.includes('at bug')).to.be.true();
73 1 expect(e.stack.includes('at AppError')).to.be.false();
74 }
75 1 done();
76 });
77 });
78
79 1 describe('subclassing', () => {
80 it('is an instance of Error', done => {
81 try {
82 1 throw new CustomError();
83 } catch(e) {
84 1 expect(e instanceof Error).to.be.true();
85 }
86 1 done();
87 });
88
89 1 it('makes the error name available', done => {
90 try {
91 1 throw new CustomError();
92 } catch(e) {
93 1 expect(e.name).to.equal('CustomError');
94 }
95 1 done();
96 });
97 });
98

Linting Report

Nothing to show here, linting is disabled.